diff --git a/frontend/src/ts/controllers/account-controller.ts b/frontend/src/ts/controllers/account-controller.ts index 456391305..70c02a120 100644 --- a/frontend/src/ts/controllers/account-controller.ts +++ b/frontend/src/ts/controllers/account-controller.ts @@ -301,6 +301,8 @@ export function signIn(): void { authListener(); LoginPage.showPreloader(); LoginPage.disableInputs(); + LoginPage.disableSignUpButton(); + LoginPage.disableSignInButton(); const email = ($(".pageLogin .login input")[0] as HTMLInputElement).value; const password = ($(".pageLogin .login input")[1] as HTMLInputElement).value; @@ -343,6 +345,8 @@ export function signIn(): void { Notifications.add(message, -1); LoginPage.hidePreloader(); LoginPage.enableInputs(); + LoginPage.enableSignInButton(); + LoginPage.updateSignupButton(); }); }); } @@ -351,6 +355,8 @@ export async function signInWithGoogle(): Promise { UpdateConfig.setChangedBeforeDb(false); LoginPage.showPreloader(); LoginPage.disableInputs(); + LoginPage.disableSignUpButton(); + LoginPage.disableSignInButton(); authListener(); const persistence = $(".pageLogin .login #rememberMe input").prop("checked") ? browserLocalPersistence @@ -380,6 +386,8 @@ export async function signInWithGoogle(): Promise { Notifications.add(message, -1); LoginPage.hidePreloader(); LoginPage.enableInputs(); + LoginPage.enableSignInButton(); + LoginPage.updateSignupButton(); }); } @@ -509,6 +517,7 @@ export function signOut(): void { async function signUp(): Promise { LoginPage.disableInputs(); + LoginPage.disableSignUpButton(); LoginPage.showPreloader(); const nname = ($(".pageLogin .register input")[0] as HTMLInputElement).value; const email = ($(".pageLogin .register input")[1] as HTMLInputElement).value; @@ -523,6 +532,7 @@ async function signUp(): Promise { if (nname === "" || email === "" || emailVerify === "" || password === "") { LoginPage.hidePreloader(); LoginPage.enableInputs(); + LoginPage.updateSignupButton(); Notifications.add("Please fill in all fields", 0); return; } @@ -534,16 +544,16 @@ async function signUp(): Promise { ) { Notifications.add("Invalid email", 0, 3); LoginPage.hidePreloader(); - $(".pageLogin .button").removeClass("disabled"); - $(".pageLogin input").prop("disabled", false); + LoginPage.enableInputs(); + LoginPage.updateSignupButton(); return; } if (email !== emailVerify) { Notifications.add("Emails do not match", 0, 3); LoginPage.hidePreloader(); - $(".pageLogin .button").removeClass("disabled"); - $(".pageLogin input").prop("disabled", false); + LoginPage.enableInputs(); + LoginPage.updateSignupButton(); return; } @@ -551,8 +561,8 @@ async function signUp(): Promise { if (password.length < 8) { Notifications.add("Password must be at least 8 characters", 0, 3); LoginPage.hidePreloader(); - $(".pageLogin .button").removeClass("disabled"); - $(".pageLogin input").prop("disabled", false); + LoginPage.enableInputs(); + LoginPage.updateSignupButton(); return; } @@ -566,16 +576,16 @@ async function signUp(): Promise { 3 ); LoginPage.hidePreloader(); - $(".pageLogin .button").removeClass("disabled"); - $(".pageLogin input").prop("disabled", false); + LoginPage.enableInputs(); + LoginPage.updateSignupButton(); return; } if (password !== passwordVerify) { Notifications.add("Passwords do not match", 0, 3); LoginPage.hidePreloader(); - $(".pageLogin .button").removeClass("disabled"); - $(".pageLogin input").prop("disabled", false); + LoginPage.enableInputs(); + LoginPage.updateSignupButton(); return; } @@ -634,8 +644,8 @@ async function signUp(): Promise { const message = Misc.createErrorMessage(e, "Failed to create account"); Notifications.add(message, -1); LoginPage.hidePreloader(); - $(".pageLogin .button").removeClass("disabled"); - $(".pageLogin input").prop("disabled", false); + LoginPage.enableInputs(); + LoginPage.updateSignupButton(); signOut(); return; } diff --git a/frontend/src/ts/controllers/input-controller.ts b/frontend/src/ts/controllers/input-controller.ts index 36f9f5d8f..62ae41e6c 100644 --- a/frontend/src/ts/controllers/input-controller.ts +++ b/frontend/src/ts/controllers/input-controller.ts @@ -615,6 +615,9 @@ function handleTab(event: JQuery.KeyDownEvent, popupVisible: boolean): void { // dont do anything special if (modalVisible) return; + // dont do anything on login so we can tab betweeen inputs + if (ActivePage.get() === "login") return; + // change page if not on test page if (ActivePage.get() !== "test") { PageController.change("test"); diff --git a/frontend/src/ts/elements/input-indicator.ts b/frontend/src/ts/elements/input-indicator.ts index 756225d03..89c9fcceb 100644 --- a/frontend/src/ts/elements/input-indicator.ts +++ b/frontend/src/ts/elements/input-indicator.ts @@ -6,18 +6,20 @@ interface InputIndicatorOption { } export class InputIndicator { + private inputElement: JQuery; private parentElement: JQuery; private options: Record; + private currentStatus: keyof typeof this.options | null; constructor( - parentElement: JQuery, + inputElement: JQuery, options: Record ) { - if (!parentElement.hasClass("inputAndIndicator")) { - throw new Error("Parent element must have class 'inputAndIndicator'"); - } - this.parentElement = parentElement; + this.inputElement = inputElement; + $(this.inputElement).wrap(`
`); + this.parentElement = $(this.inputElement).parent(".inputAndIndicator"); this.options = options; + this.currentStatus = null; let indicator = `
`; @@ -45,16 +47,20 @@ export class InputIndicator { indicator += `
`; - parentElement.append(indicator); + this.parentElement.append(indicator); } hide(): void { this.parentElement.find(".statusIndicator div").addClass("hidden"); + this.currentStatus = null; + $(this.inputElement).css("padding-right", "0.5rem"); } show(optionId: keyof typeof this.options, messageOverride?: string): void { this.hide(); + this.currentStatus = optionId; + const indicator = this.parentElement.find(`[data-option-id="${optionId}"]`); indicator.removeClass("hidden"); @@ -67,5 +73,11 @@ export class InputIndicator { } indicator.attr("aria-label", messageOverride); } + + $(this.inputElement).css("padding-right", "2.1rem"); + } + + get(): keyof typeof this.options | null { + return this.currentStatus; } } diff --git a/frontend/src/ts/pages/login.ts b/frontend/src/ts/pages/login.ts index 6a73c6896..8609b47a7 100644 --- a/frontend/src/ts/pages/login.ts +++ b/frontend/src/ts/pages/login.ts @@ -4,13 +4,27 @@ import Page from "./page"; import * as Notifications from "../elements/notifications"; import { InputIndicator } from "../elements/input-indicator"; +export function enableSignUpButton(): void { + $(".page.pageLogin .register.side .button").removeClass("disabled"); +} + +export function disableSignUpButton(): void { + $(".page.pageLogin .register.side .button").addClass("disabled"); +} + +export function enableSignInButton(): void { + $(".page.pageLogin .login.side .button").removeClass("disabled"); +} + +export function disableSignInButton(): void { + $(".page.pageLogin .login.side .button").addClass("disabled"); +} + export function enableInputs(): void { - $(".pageLogin .button").removeClass("disabled"); $(".pageLogin input").prop("disabled", false); } export function disableInputs(): void { - $(".pageLogin .button").addClass("disabled"); $(".pageLogin input").prop("disabled", true); } @@ -22,56 +36,77 @@ export function hidePreloader(): void { $(".pageLogin .preloader").addClass("hidden"); } +export const updateSignupButton = (): void => { + if ( + nameIndicator.get() !== "available" || + emailIndicator.get() !== "valid" || + verifyEmailIndicator.get() !== "match" || + passwordIndicator.get() !== "good" || + verifyPasswordIndicator.get() !== "match" + ) { + disableSignUpButton(); + } else { + enableSignUpButton(); + } +}; + const checkNameDebounced = debounce(1000, async () => { const val = $( ".page.pageLogin .register.side .usernameInput" ).val() as string; - if (!val) return; + + if (!val) { + updateSignupButton(); + return; + } const response = await Ape.users.getNameAvailability(val); if (response.status === 200) { nameIndicator.show("available", response.message); - return; - } - - if (response.status == 422) { + } else if (response.status === 422) { nameIndicator.show("unavailable", response.message); - return; - } - - if (response.status == 409) { + } else if (response.status === 409) { nameIndicator.show("taken", response.message); - return; - } - - if (response.status !== 200) { + } else { nameIndicator.show("unavailable", response.message); - return Notifications.add( + Notifications.add( "Failed to check name availability: " + response.message, -1 ); } -}); -const checkEmailsMatch = (): void => { - const email = $(".page.pageLogin .register.side .emailInput").val(); - const verifyEmail = $( - ".page.pageLogin .register.side .verifyEmailInput" - ).val(); - verifyEmailIndicator.show(email === verifyEmail ? "match" : "mismatch"); -}; + updateSignupButton(); +}); const checkEmail = (): void => { const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const email = $(".page.pageLogin .register.side .emailInput").val() as string; + if (emailRegex.test(email)) { + emailIndicator.show("valid"); + } else { + emailIndicator.show("invalid"); + } - emailIndicator.show(emailRegex.test(email) ? "valid" : "invalid"); + updateSignupButton(); +}; + +const checkEmailsMatch = (): void => { + const email = $(".page.pageLogin .register.side .emailInput").val(); + const verifyEmail = $( + ".page.pageLogin .register.side .verifyEmailInput" + ).val(); + if (email === verifyEmail) { + verifyEmailIndicator.show("match"); + } else { + verifyEmailIndicator.show("mismatch"); + } + + updateSignupButton(); }; const checkPassword = (): void => { - passwordIndicator.show("good"); const password = $( ".page.pageLogin .register.side .passwordInput" ).val() as string; @@ -80,20 +115,20 @@ const checkPassword = (): void => { if (password.length < 8) { passwordIndicator.show("short", "Password must be at least 8 characters"); return; + } else { + const hasCapital = password.match(/[A-Z]/); + const hasNumber = password.match(/[\d]/); + const hasSpecial = password.match(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/); + if (!hasCapital || !hasNumber || !hasSpecial) { + passwordIndicator.show( + "weak", + "Password must contain at least one capital letter, number, and special character" + ); + } else { + passwordIndicator.show("good", "Password is good"); + } } - - const hasCapital = password.match(/[A-Z]/); - const hasNumber = password.match(/[\d]/); - const hasSpecial = password.match(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/); - if (!hasCapital || !hasNumber || !hasSpecial) { - passwordIndicator.show( - "weak", - "Password must contain at least one capital letter, number, and special character" - ); - return; - } - - passwordIndicator.show("good", "Password is good"); + updateSignupButton(); }; const checkPasswordsMatch = (): void => { @@ -101,13 +136,17 @@ const checkPasswordsMatch = (): void => { const verifyPassword = $( ".page.pageLogin .register.side .verifyPasswordInput" ).val(); - verifyPasswordIndicator.show( - password === verifyPassword ? "match" : "mismatch" - ); + if (password === verifyPassword) { + verifyPasswordIndicator.show("match"); + } else { + verifyPasswordIndicator.show("mismatch"); + } + + updateSignupButton(); }; const nameIndicator = new InputIndicator( - $(".page.pageLogin .register.side .username.inputAndIndicator"), + $(".page.pageLogin .register.side input.usernameInput"), { available: { icon: "fa-check", @@ -130,7 +169,7 @@ const nameIndicator = new InputIndicator( ); const emailIndicator = new InputIndicator( - $(".page.pageLogin .register.side .email.inputAndIndicator"), + $(".page.pageLogin .register.side input.emailInput"), { valid: { icon: "fa-check", @@ -144,7 +183,7 @@ const emailIndicator = new InputIndicator( ); const verifyEmailIndicator = new InputIndicator( - $(".page.pageLogin .register.side .verifyEmail.inputAndIndicator"), + $(".page.pageLogin .register.side input.verifyEmailInput"), { match: { icon: "fa-check", @@ -158,7 +197,7 @@ const verifyEmailIndicator = new InputIndicator( ); const passwordIndicator = new InputIndicator( - $(".page.pageLogin .register.side .password.inputAndIndicator"), + $(".page.pageLogin .register.side input.passwordInput"), { good: { icon: "fa-check", @@ -176,7 +215,7 @@ const passwordIndicator = new InputIndicator( ); const verifyPasswordIndicator = new InputIndicator( - $(".page.pageLogin .register.side .verifyPassword.inputAndIndicator"), + $(".page.pageLogin .register.side input.verifyPasswordInput"), { match: { icon: "fa-check", diff --git a/frontend/src/ts/popups/cookie-popup.ts b/frontend/src/ts/popups/cookie-popup.ts index 51febc837..61a4f39bf 100644 --- a/frontend/src/ts/popups/cookie-popup.ts +++ b/frontend/src/ts/popups/cookie-popup.ts @@ -40,7 +40,10 @@ export function show(): void { .css("opacity", 0) .removeClass("hidden") .animate({ opacity: 1 }, 100, () => { - if ($("#cookiePopupWrapper").outerHeight(true) === 0) { + if ( + $("#cookiePopupWrapper").is(":visible") === false || + $("#cookiePopupWrapper").outerHeight(true) === 0 + ) { visible = false; } else { visible = true; diff --git a/frontend/src/ts/popups/google-sign-up-popup.ts b/frontend/src/ts/popups/google-sign-up-popup.ts index 71db3ce72..3f84c2fd0 100644 --- a/frontend/src/ts/popups/google-sign-up-popup.ts +++ b/frontend/src/ts/popups/google-sign-up-popup.ts @@ -153,28 +153,25 @@ $("#googleSignUpPopupWrapper").on("mousedown", (e) => { } }); -const nameIndicator = new InputIndicator( - $("#googleSignUpPopup .inputAndIndicator"), - { - available: { - icon: "fa-check", - level: 1, - }, - unavailable: { - icon: "fa-times", - level: -1, - }, - taken: { - icon: "fa-times", - level: -1, - }, - checking: { - icon: "fa-circle-notch", - spinIcon: true, - level: 0, - }, - } -); +const nameIndicator = new InputIndicator($("#googleSignUpPopup input"), { + available: { + icon: "fa-check", + level: 1, + }, + unavailable: { + icon: "fa-times", + level: -1, + }, + taken: { + icon: "fa-times", + level: -1, + }, + checking: { + icon: "fa-circle-notch", + spinIcon: true, + level: 0, + }, +}); const checkNameDebounced = debounce(1000, async () => { const val = $("#googleSignUpPopup input").val() as string; diff --git a/frontend/static/html/pages/login.html b/frontend/static/html/pages/login.html index cd06dc16d..4299b0534 100644 --- a/frontend/static/html/pages/login.html +++ b/frontend/static/html/pages/login.html @@ -5,49 +5,39 @@
register
-
- -
- -
- -
-
- -
-
- -
-
+ + + + + +
Sign Up
diff --git a/frontend/static/html/popups.html b/frontend/static/html/popups.html index 819f8c0d7..091cc8710 100644 --- a/frontend/static/html/popups.html +++ b/frontend/static/html/popups.html @@ -474,9 +474,7 @@
Account name
You need to choose a username before continuing
-
- -
+
Sign up
diff --git a/frontend/static/languages/_groups.json b/frontend/static/languages/_groups.json index bb6589f89..85c8e54e3 100644 --- a/frontend/static/languages/_groups.json +++ b/frontend/static/languages/_groups.json @@ -331,7 +331,8 @@ "code_matlab", "code_sql", "code_perl", - "code_php" + "code_php", + "code_vim" ] }, { diff --git a/frontend/static/languages/_list.json b/frontend/static/languages/_list.json index 88f92bbe8..c14bd1596 100644 --- a/frontend/static/languages/_list.json +++ b/frontend/static/languages/_list.json @@ -186,6 +186,7 @@ ,"code_sql" ,"code_perl" ,"code_php" + ,"code_vim" ,"hindi" ,"hindi_1k" ,"macedonian" diff --git a/frontend/static/languages/code_vim.json b/frontend/static/languages/code_vim.json new file mode 100644 index 000000000..854804a5b --- /dev/null +++ b/frontend/static/languages/code_vim.json @@ -0,0 +1,49 @@ +{ + "name": "code_vim", + "leftToRight": true, + "noLazyMode": true, + "words": [ + ":e", + ":w", + ":q", + ":q!", + ":wq", + ":wq!", + ":x", + ":x!", + ":sav", + "u", + "k", + "j", + "h", + "l", + "e", + "b", + "0", + "G", + "gg", + "L", + "[[", + "[{", + "y", + "p", + "dd", + "yy", + "y$", + "D", + ":e.", + ":Sex", + ":Sex!", + ":browse e", + ":ls", + ":cd..", + ":args", + "gf", + ":tabnew", + "gt", + ":tabfirst", + ":tablast", + ":new", + ":split" + ] +} \ No newline at end of file diff --git a/frontend/static/languages/georgian.json b/frontend/static/languages/georgian.json index 81c573a93..82c5fcbe6 100644 --- a/frontend/static/languages/georgian.json +++ b/frontend/static/languages/georgian.json @@ -42,7 +42,7 @@ "ამბებისა", "ჰიმნი", "ხაზს", - "ყველანარი", + "ყველანაირი", "რაიონი", "ძეგლი", "აღნიშნავდეს",