From 73886fc037482eb4b2d3dc631b72e3b9ac828be9 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sun, 20 Sep 2020 14:48:17 +0100 Subject: [PATCH] rewrite accounts.js in typescript slight refactor too. --- package-lock.json | 5 + package.json | 3 +- ts/accounts.ts | 339 ++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 6 + 4 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 ts/accounts.ts create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index 00dd0ca..3377f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2201,6 +2201,11 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npm.taobao.org/typescript/download/typescript-4.0.3.tgz?cache=0&sync_timestamp=1600584904815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-4.0.3.tgz", + "integrity": "sha1-FTu9Ro7wdyXB35x36LRT+NNqu6U=" + }, "uglify-js": { "version": "3.4.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", diff --git a/package.json b/package.json index 9514e29..ca18fc4 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "clean-css-cli": "^4.3.0", "lodash": "^4.17.19", "mjml": "^4.6.3", - "postcss-cli": "^7.1.1" + "postcss-cli": "^7.1.1", + "typescript": "^4.0.3" } } diff --git a/ts/accounts.ts b/ts/accounts.ts new file mode 100644 index 0000000..89cca2b --- /dev/null +++ b/ts/accounts.ts @@ -0,0 +1,339 @@ +const _post = (url: string, data: Object, onreadystatechange: () => void): void => { + let req = new XMLHttpRequest(); + req.open("POST", url, true); + req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); + req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + req.onreadystatechange = onreadystatechange; + req.send(JSON.stringify(data)); +}; + +const _get = (url: string, data: Object, onreadystatechange: () => void): void => { + let req = new XMLHttpRequest(); + req.open("GET", url, true); + req.responseType = 'json'; + req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); + req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + req.onreadystatechange = onreadystatechange; + req.send(JSON.stringify(data)); +}; + +const rmAttr = (el: HTMLElement, attr: string): void => { + if (el.classList.contains(attr)) { + el.classList.remove(attr); + } +}; +const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr); + +const Focus = (el: HTMLElement): void => rmAttr(el, 'unfocused'); +const Unfocus = (el: HTMLElement): void => addAttr(el, 'unfocused'); + +const checkCheckboxes = (): void => { + const defaultsButton = document.getElementById('accountsTabSetDefaults'); + const deleteButton = document.getElementById('accountsTabDelete'); + const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked'); + let checked = checkboxes.length; + if (checked == 0) { + Unfocus(defaultsButton); + Unfocus(deleteButton); + } else { + Focus(defaultsButton); + Focus(deleteButton); + if (checked == 1) { + deleteButton.textContent = 'Delete User'; + } else { + deleteButton.textContent = 'Delete Users'; + } + } +} + +const validateEmail = (email: string): boolean => { + const re = /\S+@\S+\.\S+/; + return re.test(email); +} + +const changeEmail = (icon: HTMLElement, id: string): void => { + const iconContent = icon.outerHTML; + icon.setAttribute('class', ''); + const entry: HTMLInputElement = icon.nextElementSibling; + const ogEmail = entry.value; + entry.readOnly = false; + entry.classList.remove('form-control-plaintext'); + entry.classList.add('form-control'); + if (ogEmail == "") { + entry.placeholder = 'Address'; + } + const tick = document.createElement('i'); + tick.outerHTML = ` + + `; + tick.onclick = (): void => { + const newEmail = entry.value; + if (!validateEmail(newEmail) || newEmail == ogEmail) { + return; + } + cross.remove(); + tick.outerHTML = ` +
+ Saving... +
+ `; + let send = {}; + send[id] = newEmail; + _post("/modifyEmails", send, function (): void { + if (this.readyState == 4) { + if (this.status == '200' || this.status == '204') { + entry.nextElementSibling.remove(); + } else { + entry.value = ogEmail; + } + } + }); + icon.outerHTML = iconContent; + entry.readOnly = true; + entry.classList.remove('form-control'); + entry.classList.add('form-control-plaintext'); + entry.placeholder = ''; + }; + const cross: HTMLElement = document.createElement('i'); + cross.outerHTML = ` + + `; + cross.onclick = (): void => { + tick.remove(); + cross.remove(); + icon.outerHTML = iconContent; + entry.readOnly = true; + entry.classList.remove('form-control'); + entry.classList.add('form-control-plaintext'); + entry.placeholder = ''; + entry.value = ogEmail; + }; + icon.parentNode.appendChild(tick); + icon.parentNode.appendChild(cross); +}; + +var jfUsers: Array; + +const populateUsers = (): void => { + const acList = document.getElementById('accountsList'); + acList.innerHTML = ` +
+ Getting Users... + +
+ `; + Unfocus(acList.parentNode.querySelector('thead')); + const accountsList = document.createElement('tbody'); + accountsList.id = 'accountsList'; + const generateEmail = (id: string, name: string, email: string): string => { + let entry: HTMLDivElement = document.createElement('div'); + entry.id = 'email_' + id; + let emailValue: string = email; + if (emailValue == undefined) { + emailValue = ""; + } + entry.innerHTML = ` + + + `; + return entry.outerHTML; + }; + const template = (id: string, username: string, email: string, lastActive: string, admin: boolean): string => { + let isAdmin = "No"; + if (admin) { + isAdmin = "Yes"; + } + let fci = "form-check-input"; + if (bsVersion != 5) { + fci = ""; + } + return ` + + ${username} + ${generateEmail(id, name, email)} + ${lastActive} + ${isAdmin} + `; + }; + + _get("/getUsers", null, function (): void { + if (this.readyState == 4 && this.status == '200') { + jfUsers = this.response['users']; + for (const user of jfUsers) { + let tr = document.createElement('tr'); + tr.innerHTML = template(user['id'], user['name'], user['email'], user['last_active'], user['admin']); + accountsList.appendChild(tr); + } + Focus(acList.parentNode.querySelector('thead')); + acList.replaceWith(accountsList); + } + }); +} + +(document.getElementById('selectAll')).onclick = function (): void { + const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]'); + for (const i in checkboxes) { + checkboxes[i].checked = (this).checked; + } +}; + +(document.getElementById('deleteModalNotify')).onclick = function (): void { + const textbox: HTMLElement = document.getElementById('deleteModalReasonBox'); + if ((this).checked) { + Focus(textbox); + } else { + Unfocus(textbox); + } +}; + +(document.getElementById('accountsTabDelete')).onclick =function (): void { + const deleteButton: HTMLButtonElement = this; + const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked'); + let selected: Array = new Array(checkboxes.length); + for (const i in checkboxes){ + selected[i] = checkboxes[i].id.replace("select_", ""); + } + let title = " user"; + let msg = "Notify user"; + if (selected.length > 1) { + title += "s"; + msg += "s"; + } + title = `Delete ${selected.length} ${title}`; + msg += " of account deletion"; + + document.getElementById('deleteModalTitle').textContent = title; + const dmNotify: HTMLInputElement = document.getElementById('deleteModalNotify') + dmNotify.checked = false; + document.getElementById('deleteModalNotifyLabel').textContent = msg; + const dmReason: HTMLTextAreaElement = document.getElementById('deleteModalReason') + dmReason.value = ''; + Unfocus(document.getElementById('deleteModalReasonBox')); + const dmSend: HTMLButtonElement = document.getElementById('deleteModalSend'); + dmSend.textContent = 'Delete'; + dmSend.onclick = function (): void { + const button: HTMLButtonElement = this; + const send = { + 'users': selected, + 'notify': dmNotify.checked, + 'reason': dmReason.value + }; + _post("/deleteUser", send, function (): void { + if (this.readyState == 4) { + if (this.status == '500') { + if ("error" in req.reponse) { + button.textContent = 'Failed'; + } else { + button.textContent = 'Partial fail (check console)'; + console.log(req.response); + } + setTimeout((): void => { + Unfocus(deleteButton); + deleteModal.hide(); + }, 4000); + } else { + Unfocus(deleteButton); + deleteModal.hide() + } + populateUsers(); + checkCheckboxes(); + } + }); + }; + deleteModal.show(); +}; + +(document.getElementById('selectAll')).checked = false; + +(document.getElementById('accountsTabSetDefaults')).onclick = function (): void { + const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked'); + let userIDs: Array = new Array(checkboxes.length); + for (const i in checkboxes){ + userIDs[i] = checkboxes[i].id.replace("select_", ""); + } + if (userIDs.length == 0) { + return; + } + populateRadios(); + let userString = 'user'; + if (userIDs.length > 1) { + userString += "s"; + } + document.getElementById('defaultsTitle').textContent = `Apply settings to ${userIDs.length} ${userString}`; + document.getElementById('userDefaultsDescription').textContent = ` + Create an account and configure it to your liking, then choose it from below to apply to your selected users. + `; + document.getElementById('storeHomescreenLabel').textContent = `Apply homescreen layout`; + Focus(document.getElementById('defaultsSourceSection')); + (document.getElementById('defaultsSource')).value = 'userTemplate'; + Unfocus(document.getElementById('defaultUserRadios')); + document.getElementById('storeDefaults').onclick = (): void => storeDefaults(userIDs); + userDefaultsModal.show(); +}; + +(document.getElementById('defaultsSource')).addEventListener('change', function (): void { + const radios = document.getElementById('defaultUserRadios'); + if (this.value == 'userTemplate') { + Unfocus(radios); + } else { + Focus(radios); + } +}); + +(document.getElementById('newUserCreate')).onclick = function (): void { + const ogText = this.textContent; + this.innerHTML = ` + Creating... + `; + const email: string = (document.getElementById('newUserEmail')).value; + var username: string = email; + if (document.getElementById('newUserName') != null) { + username = (document.getElementById('newUserName')).value; + } + const password: string = (document.getElementById('newUserPassword')).value; + if (!validateEmail(email) && email != "") { + return; + } + const send = { + 'username': username, + 'password': password, + 'email': email + }; + const button: HTMLButtonElement = this; + _post("/newUserAdmin", send, function (): void { + if (this.readyState == 4) { + rmAttr(button, 'btn-primary'); + if (this.status == '200') { + addAttr(button, 'btn-success'); + button.textContent = 'Success'; + setTimeout((): void => { + rmAttr(button, 'btn-success'); + addAttr('btn-primary'); + button.textContent = ogText; + newUserModal.hide(); + }, 1000); + } else { + addAttr(button, 'btn-danger'); + if ("error" in req.response) { + button.textContent = req.response["error"]; + } else { + button.textContent = 'Failed'; + } + setTimeout((): void => { + rmAttr(button, 'btn-danger'); + addAttr(button, 'btn-primary'); + button.textContent = ogText; + }, 2000); + } + } + }); +}; + +(document.getElementById('accountsTabAddUser')).onclick = function (): void { + (document.getElementById('newUserEmail')).value = ''; + (document.getElementById('newUserPassword')).value = ''; + if (document.getElementById('newUserName') != null) { + (document.getElementById('newUserName')).value = ''; + } + newUserModal.show(); +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a84c31d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": ["dom", "es2017"] + } +}