/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 Rainbownapkin and the TTN Community This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ class profileUpdatePrompt{ constructor(field){ this.field = field; this.contentNode = document.querySelector(`#profile-${field}-content`); this.promptNode = document.querySelector(`#profile-${field}-prompt`); if(this.promptNode != null){ this.setupInput(); } } setupInput(){ this.contentNode.addEventListener('click', this.popPrompt.bind(this)); this.promptNode.addEventListener('keydown', this.closePrompt.bind(this)); } popPrompt(){ this.promptNode.style.display = 'inline'; this.promptNode.focus(); } closePrompt(event){ //Check if we're finished const fin = event.key == "Escape" || (event.key == "Enter" && !event.shiftKey); //IF we are if(fin){ //Display the prompt this.promptNode.style.display = 'none'; } //Return whether or not we're done return fin; } } class profileUpdateTextPrompt extends profileUpdatePrompt{ constructor(field, fillPrompt = false){ //call derived constructor super(field); //Set fill prompt this.fillPrompt = fillPrompt; } popPrompt(){ //Call derived method super.popPrompt(); //For each line break this.contentNode.querySelectorAll('br').forEach((br)=>{ //Replace the linenreaks with newlines to get the text area to play nice br.outerHTML = "\n" }); //If we're filling the prompt if(this.fillPrompt){ //Fill the prompt content and placeholder this.promptNode.value = this.contentNode.textContent; this.promptNode.placeholder = this.field; //otherwise }else{ //Just fill the placeholder this.promptNode.placeholder = this.contentNode.textContent; } //Hide content this.contentNode.style.display = 'none'; } async closePrompt(event){ //Call derived constructor and check if we're finished, assuming we're finished... if(super.closePrompt(event)){ //Display this.contentNode.style.display = 'inline-block'; //If we're not cancelling if(event.key == "Enter" && !event.shiftKey){ //Create empty update object let updateObj = {}; //Fill field updateObj[this.field] = this.promptNode.value; //Send er' off const update = await utils.ajax.updateProfile(updateObj); //Fill content from update, make sure to add line breaks for the bio this.contentNode.textContent = utils.unescapeEntities(update[this.field].replaceAll('\n','
')); } } } } class profileUpdateImagePrompt extends profileUpdatePrompt{ constructor(){ //call derived constructor super('img'); } popPrompt(){ //Call derived method super.popPrompt(); this.promptNode.placeholder = this.contentNode.src; } async closePrompt(event){ //Call derived constructor super.closePrompt(event); //If we're not cancelling if(event.key == "Enter"){ //Create empty update object let updateObj = {}; //Fill field updateObj[this.field] = this.promptNode.value; //Send er' off const update = await utils.ajax.updateProfile(updateObj); //Fill content from update this.contentNode.src = update[this.field]; } } } class passwordResetPrompt{ constructor(){ this.oldPassNode = document.querySelector('#account-settings-password-reset-old'); this.newPassNode = document.querySelector('#account-settings-password-reset-new'); this.confirmPassNode = document.querySelector('#account-settings-password-reset-confirm'); this.setupInput(this.oldPassNode); this.setupInput(this.newPassNode); this.setupInput(this.confirmPassNode); } setupInput(node){ if(node != null){ node.addEventListener("keydown", this.update.bind(this)); } } async update(event){ var hasVal = (this.oldPassNode.value && this.newPassNode.value && this.confirmPassNode.value); if((!event || event.key == "Enter") && hasVal){ if(this.newPassNode.value == this.confirmPassNode.value){ const updateObj = {}; updateObj.passChange = { oldPass: this.oldPassNode.value, newPass: this.newPassNode.value, confirmPass: this.confirmPassNode.value }; const response = await utils.ajax.updateProfile(updateObj); if(response != null){ //Return user homepage after good pass change, as we've probably been logged out by the server for security. window.location.pathname = '/'; } } } } } class tokeList{ constructor(){ this.tokeList = document.querySelector('#profile-tokes'); this.tokeListLabel = document.querySelector('.profile-toke-count'); this.tokeListToggleIcon = document.querySelector('#toggle-toke-list'); this.setupInput(); } setupInput(){ this.tokeListLabel.addEventListener('click', this.toggleTokeList.bind(this)); } toggleTokeList(){ if(this.tokeList.style.visibility == "visible"){ this.tokeList.style.visibility = "collapse"; this.tokeListToggleIcon.classList.replace("bi-caret-down-fill","bi-caret-left-fill"); }else{ this.tokeList.style.visibility = "visible"; this.tokeListToggleIcon.classList.replace("bi-caret-left-fill","bi-caret-down-fill"); } } } class accountSettingsButton{ constructor(field, popupClass){ this.deleteLink = document.querySelector(`#account-settings-${field}-button`); this.popupClass = popupClass; if(this.deleteLink != null){ this.setupInput(); } } setupInput(){ this.deleteLink.addEventListener("click",this.showPopup.bind(this)); } async showPopup(event){ this.popup = new this.popupClass(); } } class changeEmailPopup{ constructor(){ this.popup = new canopyUXUtils.popup("changeEmail", true, this.asyncConstructor.bind(this)); } asyncConstructor(){ this.emailPrompt = document.querySelector('#email-change-popup-email'); this.passPrompt = document.querySelector('#email-change-popup-password'); this.setupInput(); } setupInput(){ this.emailPrompt.addEventListener('keydown', this.emailRequest.bind(this)); this.passPrompt.addEventListener('keydown', this.emailRequest.bind(this)); } async emailRequest(event){ if(event.key == "Enter"){ await utils.ajax.requestEmailChange(this.emailPrompt.value, this.passPrompt.value); } } } class changePasswordPopup{ constructor(){ this.popup = new canopyUXUtils.popup("changePassword", true, this.asyncConstructor.bind(this)); } asyncConstructor(){ this.oldPassPrompt = document.querySelector('#password-change-popup-old-password'); this.newPassPrompt = document.querySelector('#password-change-popup-new-password'); this.confirmPassPrompt = document.querySelector('#password-change-popup-confirm-new-password'); this.setupInput(); } setupInput(){ this.oldPassPrompt.addEventListener('keydown', this.emailRequest.bind(this)); this.newPassPrompt.addEventListener('keydown', this.emailRequest.bind(this)); this.confirmPassPrompt.addEventListener('keydown', this.emailRequest.bind(this)); } async emailRequest(event){ if(event.key == "Enter"){ const updateObj = {}; updateObj.passChange = { oldPass: this.oldPassPrompt.value, newPass: this.newPassPrompt.value, confirmPass: this.confirmPassPrompt.value }; const response = await utils.ajax.updateProfile(updateObj); if(response != null){ //Return user homepage after good pass change, as we've probably been logged out by the server for security. window.location.pathname = '/'; } } } } class deleteAccountPopup{ constructor(){ this.popup = new canopyUXUtils.popup("nukeUser", true, this.asyncConstructor.bind(this)); } asyncConstructor(){ this.passwordPrompt = document.querySelector("#delete-account-popup-password"); this.setupInput(); } setupInput(){ this.passwordPrompt.addEventListener("keydown", this.nukeAccount.bind(this)); } async nukeAccount(event){ if(event.key == "Enter"){ await utils.ajax.deleteAccount(event.target.value); } } } //Object Instantiation const imgPrompt = new profileUpdateImagePrompt(); const pronounsPrompt = new profileUpdateTextPrompt('pronouns'); const signaturePrompt = new profileUpdateTextPrompt('signature'); const bioPrompt = new profileUpdateTextPrompt('bio', true); const profileTokeList = new tokeList(); const accountEmailChange = new accountSettingsButton('update-email', changeEmailPopup); const accountPasswordChange = new accountSettingsButton('change-password', changePasswordPopup); const accountDeleteButton = new accountSettingsButton('delete', deleteAccountPopup);