PageSourceSearch

https://thehighwire.com/wp-content/themes/thehighwire/js/hw-plus-form-utils.js

js thehighwire.com collected 2026-10-02 02:25:04 UTC 7,757 bytes, 224 lines download raw bytes

1const validateName = e => e.trim().match(/^[a-zA-Z\- ]{1,50}$/) !== null;
2
3const validateEmail = e => e.trim().match(/^(?=.{3,320}$)[-!#$%&'*+/0-9=?A-Z^_a-z{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z{|}~]){0,63}@[a-zA-Z0-9](-?[a-zA-Z0-9])*(\.[a-zA-Z](-?[a-zA-Z0-9])*)+$/) !== null;
4
5const validateString = e => e.trim().match(/^[a-zA-Z0-9_-]{3,40}$/) !== null;
6
7const validatePassword = e => {
8    if (e.length < 8) {
9        return false;
10    }
11    const t = /(.)\1{3,}/;
12    const a = /0123|1234|2345|3456|4567|5678|6789|7890|abcd|bcde|cdef|defg|efgh|fghi|ghij|hijk|ijkl|jklm|klmn|lmno|mnop|nopq|opqr|pqrs|qrst|rstu|stuv|tuvw|uvwx|vwxy|wxyz/i;
13    const r = /3210|4321|5432|6543|7654|8765|9876|0987|dcba|edcb|fedc|gfed|hgfed|ihgfe|jihgf|lkji|mlkj|nmlk|onml|ponm|qpon|rqp|srqp|utsr|vuts|wvut|xwvu|yxwv|zyxw/i;
14    const s = !t.test(e) && !a.test(e) && !r.test(e);
15    return s;
16};
17
18const validatePasswordMatch = (e, t) => e === t.value;
19
20const validationData = {
21    "#plus-signup-first-name": {
22        validate: validateName,
23        message: "Please enter a valid first name"
24    },
25    "#plus-signup-last-name": {
26        validate: validateName,
27        message: "Please enter a valid last name"
28    },
29    "#plus-login-email": {
30        validate: validateEmail,
31        message: "Please enter a valid email address"
32    },
33    "#plus-signup-email": {
34        validate: validateEmail,
35        message: "Please enter a valid email address"
36    },
37    "#plus-signup-username": {
38        validate: validateString,
39        message: `Please ensure your username meets the following criteria:<ul><li>Alphanumeric, length between 3 and 40 characters</li><li>Allowed special characters: hyphens, underscores</li></ul>`
40    },
41    "#plus-signup-password": {
42        validate: validatePassword,
43        message: `Please ensure your password meets the following criteria:<ul>\n    <li>Minimum length of 8 characters</li><li>No more than 3 characters repeated or in sequential order</li></ul>`
44    },
45    "#plus-signup-password-confirm": {
46        validate: validatePasswordMatch,
47        message: "Passwords do not match"
48    },
49    "#plus-account-first-name": {
50        validate: validateName,
51        message: "Please enter a valid first name"
52    },
53    "#plus-account-last-name": {
54        validate: validateName,
55        message: "Please enter a valid last name"
56    },
57    "#plus-account-email": {
58        validate: validateEmail,
59        message: "Please enter a valid email address"
60    },
61    "#plus-account-display-name": {
62        validate: validateString,
63        message: `Please ensure your username meets the following criteria:<ul><li>Alphanumeric, length between 3 and 40 characters</li><li>Allowed special characters: hyphens, underscores</li></ul>`
64    },
65    "#plus-account-password": {
66        validate: validatePassword,
67        message: `Please ensure your password meets the following criteria:<ul>\n    <li>Minimum length of 8 characters</li><li>No more than 3 characters repeated or in sequential order</li></ul>`
68    },
69    "#plus-account-password-confirm": {
70        validate: validatePasswordMatch,
71        message: "Passwords do not match"
72    }
73};
74
75export const formStatus = {};
76
77export function passwordVisibility() {
78    const e = document.querySelectorAll(".plus-form__field-wrap--password");
79    if (!e) return;
80    e.forEach(e => {
81        const t = e.querySelector(".plus-form__field--password");
82        const a = e.querySelector(".plus-form__field--pass-visibility");
83        const r = () => {
84            t.type = a.checked ? "text" : "password";
85            a.nextElementSibling.classList.toggle("shown", a.checked);
86        };
87        a.addEventListener("click", r);
88        r();
89    });
90};
91
92export function placeholderLabels() {
93    const e = document.querySelectorAll(".plus-form__label:not(.plus-form__label--checkbox)");
94    if (!e) return;
95    e.forEach(e => {
96        const t = getFieldForLabel(e);
97        t.addEventListener("focus", () => fieldFocusHandler(t, e));
98        t.addEventListener("blur", () => fieldBlurHandler(t, e));
99    });
100};
101
102export function updateFormStatus(e) {
103    const t = e.querySelectorAll(".plus-form__label");
104    formStatus[e.id] = Array.from(t).map(e => getFieldForLabel(e).classList.contains("error"));
105    e.firstElementChild.setAttribute("aria-hidden", !formStatus[e.id].includes(true));
106};
107
108export function fieldFocusHandler(e, t) {
109    clearFieldError(e);
110    if (!e.value) {
111        t.classList.add("is-label");
112    }
113};
114
115export function fieldBlurHandler(e, t) {
116    const a = e.value;
117    if (a) {
118        const t = validateFormField(e);
119        if (!t) {
120            const t = validationData[`#${e.id}`];
121            const a = t && t.message ? t.message : "Input invalid";
122            showFieldError(e, a);
123        }
124    } else if (e.required) {
125        showFieldError(e, "Please fill in this form field");
126        t.classList.toggle("is-label");
127    } else {
128        t.classList.toggle("is-label");
129    }
130};
131
132export function getFieldForLabel(e) {
133    const t = e.getAttribute("for");
134    if (!t) return false;
135    return document.querySelector(`#${t}`);
136};
137
138export function getLabelForField(e) {
139    return document.querySelector(`.plus-form__label[for='${e.id}']`);
140};
141
142export function showFieldError(e, t, a = true) {
143    if (e.classList.contains("error")) return;
144    const r = e.closest("form");
145    e.setAttribute("aria-describedby", `${e.id}_error`);
146    const s = e.closest(".plus-form__field-wrap");
147    const l = t ? t : "Field required, please fill in this form field";
148    const i = s.querySelector(".plus-form__error-label");
149    i.id = `${e.id}_error`;
150    i.setAttribute("role", "alert");
151    i.setAttribute("aria-hidden", false);
152    i.innerHTML = l;
153    e.classList.add("error");
154    s.classList.add("error");
155    updateFormStatus(r);
156    if (a) {
157        r.firstElementChild.innerHTML = "Please fix the form fields in error.";
158    }
159};
160
161export function clearFieldError(e) {
162    e.removeAttribute("aria-describedby");
163    const t = e.closest(".plus-form__field-wrap");
164    const a = document.querySelector(`#${e.id}_error`);
165    if (!a) return;
166    a.removeAttribute("role");
167    a.setAttribute("aria-hidden", true);
168    a.id = "";
169    e.classList.remove("error");
170    t.classList.remove("error");
171    const r = e.closest("form");
172    updateFormStatus(r);
173};
174
175export function validateFormField(e) {
176    const t = e.id;
177    if (!t) return false;
178    const a = e.value.trim();
179    if (!a) return false;
180    const r = validationData[`#${t}`] && typeof validationData[`#${t}`].validate === "function" ? validationData[`#${t}`].validate : e => !!e;
181    if (t.includes("password-confirm")) {
182        const e = t.replace("-confirm", "");
183        const s = document.querySelector(`#${e}`);
184        if (!s) {
185            console.error("Password field not found, please check form field ids.", t, e);
186            return;
187        }
188        return r(a, s);
189    }
190    return r(a);
191};
192
193export async function submitForm(e, t) {
194    const a = await fetch(e, {
195        method: "POST",
196        credentials: "same-origin",
197        body: t
198    });
199    return a.json();
200};
201
202export function submitValidationAsync(e) {
203    return new Promise(t => {
204        let a = false;
205        if (e.required) {
206            if (e.value === "") {
207                showFieldError(e);
208                a = true;
209            }
210            if (e.id === "plus-signup-consent" && !e.checked) {
211                showFieldError(e, "Please consent to the terms and privacy policy to signup");
212                a = true;
213            }
214        }
215        t(a);
216    });
217};
218
219export function submitWithErrors(e) {
220    e.classList.add("emphasize");
221    setTimeout(() => {
222        e.classList.remove("emphasize");
223    }, 600);
224};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.