ui.js (6068B)
1 import * as api from "/lib/api.js" 2 import * as etc from "/lib/etc.js" 3 4 export function input(name, memo, options) { 5 if (!name) { 6 throw new Error("No name provided") 7 } 8 9 let e = document.createElement("input") 10 e.name = name 11 e.placeholder = name 12 e.setAttribute("title", memo) 13 14 if (!options) { 15 options = {} 16 } 17 if (options.attributes) { 18 for (let i in options.attributes) { 19 console.debug("Input", name, i, options.attributes[i]) 20 e.setAttribute(i, options.attributes[i]) 21 } 22 } 23 if (options.handlers) { 24 for (let i in options.handlers) { 25 e.addEventListener(i, options.handlers[i], false) 26 } 27 } 28 29 return e 30 } 31 32 export function button(name, memo, icon, options) { 33 let button = input(name, memo, options) 34 let attrs 35 if (icon == null) { 36 attrs = { 37 type: "button", 38 value: name 39 } 40 } else { 41 attrs = { 42 alt: name, 43 type: "image", 44 class: "icon", 45 src: "/icons/" + icon + "-outline.svg" 46 } 47 } 48 for (let i in attrs) { 49 console.debug("Button", name, i, attrs[i]) 50 button.setAttribute(i, attrs[i]) 51 } 52 53 return button 54 } 55 56 export function toggle(a, b, options) { 57 if (!options) { 58 options = {} 59 } 60 if (options.swap) { 61 let t = a 62 a = b 63 b = t 64 } 65 if (options.init) { 66 // Should this be run like in the event listener, handling .then methods? 67 b.func() 68 } 69 toggle_setup_button(a, b) 70 toggle_setup_button(b, a) 71 return a.button 72 } 73 74 function toggle_setup_button(a, b) { 75 a.button.addEventListener("click", function() { 76 let swap = function() { a.button.replaceWith(b.button) } 77 let r = a.func() 78 if (r && typeof r.then == "function") { 79 r.then(swap) 80 } else { 81 swap() 82 } 83 }, false) 84 } 85 86 export function login(options) { 87 options = options ?? {} 88 89 let form = document.createElement("form") 90 form.classList.add("credentials") 91 92 let h = form.appendChild(document.createElement("h1")) 93 h.append(document.createTextNode("Login")) 94 95 if (!options.user) { 96 let aside = form.appendChild(document.createElement("aside")) 97 aside.append(document.createTextNode("Don't have an account? ")) 98 let a = aside.appendChild(document.createElement("a")) 99 a.href = "/register" 100 a.append(document.createTextNode("Signup")) 101 aside.append(document.createTextNode(" now!")) 102 } 103 104 let label = form.appendChild(document.createElement("label")) 105 label.appendChild(document.createTextNode("Username:")) 106 label.setAttribute("for", "username") 107 let u = form.appendChild(usernameInput()) 108 if (options.user) { 109 u.value = options.user 110 if (options.forceUser) { 111 u.setAttribute("disabled", true) 112 } 113 } 114 115 label = form.appendChild(document.createElement("label")) 116 label.appendChild(document.createTextNode("Password:")) 117 label.setAttribute("for", "password") 118 form.appendChild(passwordInput()) 119 120 let button = form.appendChild(document.createElement("input")) 121 button.setAttribute("type", "submit") 122 button.setAttribute("value", "Login") 123 124 form.addEventListener("submit", function(event) { 125 event.preventDefault() 126 api.login(username.value, password.value) 127 .then(function() { 128 form.remove() 129 if (options.callback != null) { 130 options.callback() 131 } 132 }) 133 .catch(function(err) { 134 etc.error(err + ": Unable to login", form) 135 }) 136 }) 137 138 return form 139 } 140 141 export function usernameInput() { 142 let username = document.createElement("input") 143 username.id = "username" 144 username.setAttribute("autocomplete", "username") 145 username.setAttribute("name", "username") 146 username.setAttribute("minlength", 3) 147 username.setAttribute("maxlength", 32) 148 username.setAttribute("pattern", "^[^@]*$") 149 username.setAttribute("autocapitalize", "none") 150 username.setAttribute("spellcheck", "false") 151 username.setAttribute("autocorrect", "off") 152 username.addEventListener("change", function(ev) { 153 let v = ev.target.validity 154 if (v.tooShort || v.tooLong) { 155 ev.target.setCustomValidity("Usernames must be between 3-32 characters long.") 156 } else if (v.patternMismatch) { 157 ev.target.setCustomValidity("Usernames cannot contain the @ sign") 158 } else { 159 ev.target.setCustomValidity("") 160 return 161 } 162 163 ev.target.reportValidity() 164 }) 165 166 return username 167 } 168 169 export function passwordInput(options) { 170 options = options ?? {} 171 let password = document.createElement("input") 172 173 password.id = "password" 174 password.setAttribute("autocomplete", options.new ? "new-password" : "current-password") 175 password.setAttribute("type", "password") 176 password.setAttribute("name", "password") 177 password.setAttribute("minlength", 8) 178 password.setAttribute("maxlength", 72) 179 password.addEventListener("change", function(ev) { 180 let v = ev.target.validity 181 if (v.tooShort || v.tooLong) { 182 ev.target.setCustomValidity("Passwords must be between 8-72 characters long.") 183 } else { 184 ev.target.setCustomValidity("") 185 return 186 } 187 188 ev.target.reportValidity() 189 }) 190 191 return password 192 } 193 194 export function prettyName(name, options) { 195 options = options ?? {} 196 options.separator = options.separator ?? /[-_]/ 197 options.title = options.title ?? true 198 199 let words = name.split(options.separator) 200 for (let i in words) { 201 words[i] = capitalize(words[i]) 202 if (!options.title) { 203 break 204 } 205 } 206 207 return words.join(" ") 208 } 209 210 export function capitalize(word) { 211 return word.charAt(0).toUpperCase() + word.substr(1) 212 } 213 214 export function warning(content) { 215 let warning = document.createElement("span") 216 warning.classList.add("warning") 217 218 let icon = warning.appendChild( 219 document.createElement("img") 220 ) 221 icon.classList.add("icon") 222 icon.setAttribute("src", "/icons/warning-outline.svg") 223 224 if (typeof content === "string") { 225 let s = content 226 content = document.createElement("p") 227 content.appendChild( 228 document.createTextNode(s) 229 ) 230 } 231 232 // appendChild can make sure it's correct 233 warning.appendChild(content) 234 content.classList.add("content") 235 236 return warning 237 } 238 239 export function wait(content) { 240 let wait = document.getElementById("wait") 241 if (!content) { 242 wait.remove() 243 } 244 245 if (!wait) { 246 wait = document.body.appendChild(document.createElement("div")) 247 wait.id = "wait" 248 } 249 250 if (typeof content === "string") { 251 content = document.createTextNode(content) 252 } 253 wait.replaceChildren(content) 254 }