Compare commits
2 commits
3fc8104bdd
...
735ca360c8
Author | SHA1 | Date | |
---|---|---|---|
735ca360c8 | |||
265d774b4f |
5 changed files with 205 additions and 21 deletions
5
spec.js
5
spec.js
|
@ -84,6 +84,11 @@ module.exports = [
|
|||
source: "/js/functions.js",
|
||||
target: "/static/functions.js",
|
||||
},
|
||||
{
|
||||
type: "js",
|
||||
source: "/js/login.js",
|
||||
target: "/static/login.js",
|
||||
},
|
||||
{
|
||||
type: "file",
|
||||
source: "/assets/fonts/whitney-500.woff",
|
||||
|
|
148
src/js/login.js
Normal file
148
src/js/login.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
import {q, ElemJS, ejs} from $to_relative "/js/basic.js"
|
||||
|
||||
const password = q("#password")
|
||||
const homeserver = q("#homeserver")
|
||||
|
||||
class Username extends ElemJS {
|
||||
constructor() {
|
||||
super(q("#username"))
|
||||
|
||||
this.on("change", this.updateServer.bind(this))
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this.element.value.match(/^@?[a-z0-9._=\/-]+(?::[a-zA-Z0-9.:\[\]-]+)?$/)
|
||||
}
|
||||
|
||||
getUsername() {
|
||||
return this.element.value.match(/^@?([a-z0-9._=\/-]+)/)[1]
|
||||
}
|
||||
|
||||
getServer() {
|
||||
const server = this.element.value.match(/^@?[a-z0-9._=\?-]+:([a-zA-Z0-9.:\[\]-]+)$/)
|
||||
if (server && server[1]) return server[1]
|
||||
else return null
|
||||
}
|
||||
|
||||
updateServer() {
|
||||
if (!this.isValid()) return
|
||||
if (this.getServer()) homeserver.value = this.getServer()
|
||||
}
|
||||
}
|
||||
|
||||
const username = new Username()
|
||||
|
||||
class Feedback extends ElemJS {
|
||||
constructor() {
|
||||
super(q("#feedback"))
|
||||
this.loading = false
|
||||
this.loadingIcon = ejs("span").class("loading-icon")
|
||||
this.messageSpan = ejs("span")
|
||||
this.child(this.messageSpan)
|
||||
}
|
||||
|
||||
setLoading(state) {
|
||||
if (this.loading && !state) {
|
||||
this.loadingIcon.remove()
|
||||
} else if (!this.loading && state) {
|
||||
this.childAt(0, this.loadingIcon)
|
||||
}
|
||||
this.loading = state
|
||||
}
|
||||
|
||||
message(content) {
|
||||
if (content) {
|
||||
this.class("form-feedback")
|
||||
} else {
|
||||
this.removeClass("form-feedback")
|
||||
}
|
||||
this.messageSpan.text(content)
|
||||
}
|
||||
}
|
||||
|
||||
const feedback = new Feedback()
|
||||
|
||||
class Form extends ElemJS {
|
||||
constructor() {
|
||||
super(q("#form"))
|
||||
|
||||
this.processing = false
|
||||
|
||||
this.on("submit", this.submit.bind(this))
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.processing) return
|
||||
this.processing = true
|
||||
if (!username.isValid()) return this.cancel("Username is not valid.")
|
||||
|
||||
// Resolve homeserver address
|
||||
let currentAddress = homeserver.value
|
||||
let ok = false
|
||||
while (!ok) {
|
||||
if (!currentAddress.match(/^https?:\/\//)) currentAddress = "https://" + currentAddress
|
||||
currentAddress = currentAddress.replace(/\/*$/, "")
|
||||
this.status(`Looking up homeserver... trying ${currentAddress}`)
|
||||
try {
|
||||
// check if we found the actual matrix server
|
||||
try {
|
||||
const versions = await fetch(`${currentAddress}/_matrix/client/versions`).then(res => res.json())
|
||||
if (Array.isArray(versions.versions)) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
} catch (e) {}
|
||||
// find the next matrix server in the chain
|
||||
const root = await fetch(`${currentAddress}/.well-known/matrix/client`).then(res => res.json())
|
||||
let nextAddress = root["m.homeserver"].base_url
|
||||
nextAddress = nextAddress.replace(/\/*$/, "")
|
||||
if (currentAddress === nextAddress) {
|
||||
ok = true
|
||||
}
|
||||
currentAddress = nextAddress
|
||||
} catch (e) {
|
||||
return this.cancel(`Failed to look up server ${currentAddress}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Request access token
|
||||
this.status("Logging in...")
|
||||
const root = await fetch(`${currentAddress}/_matrix/client/r0/login`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
type: "m.login.password",
|
||||
user: username.getUsername(),
|
||||
password: password.value
|
||||
})
|
||||
}).then(res => res.json())
|
||||
|
||||
if (!root.access_token) {
|
||||
if (root.error) {
|
||||
this.cancel(`Server said: ${root.error}`)
|
||||
} else {
|
||||
this.cancel("Login mysteriously failed.")
|
||||
console.error(root)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.setItem("mx_user_id", root.user_id)
|
||||
localStorage.setItem("domain", currentAddress)
|
||||
localStorage.setItem("access_token", root.access_token)
|
||||
|
||||
location.assign("./")
|
||||
}
|
||||
|
||||
status(message) {
|
||||
feedback.setLoading(true)
|
||||
feedback.message(message)
|
||||
}
|
||||
|
||||
cancel(message) {
|
||||
this.processing = false
|
||||
feedback.setLoading(false)
|
||||
feedback.message(message)
|
||||
}
|
||||
}
|
||||
|
||||
const form = new Form()
|
|
@ -108,10 +108,12 @@ class Room extends ElemJS {
|
|||
getIcon() {
|
||||
const avatar = this.data.state.events.find(e => e.type === "m.room.avatar")
|
||||
if (avatar) {
|
||||
return resolveMxc(avatar.content.url || avatar.content.avatar_url, 32, "crop")
|
||||
} else {
|
||||
return null
|
||||
const url = avatar.content.url || avatar.content.avatar_url
|
||||
if (url) {
|
||||
return resolveMxc(url, 32, "crop")
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
isDirect() {
|
||||
|
|
|
@ -2,19 +2,20 @@ doctype html
|
|||
html
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
link(rel="stylesheet" type="text/css" href=getStatic("/sass/login.sass"))
|
||||
title Carbon
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||
link(rel="stylesheet" type="text/css" href=getStatic("/sass/login.sass"))
|
||||
script(type="module" src=getStatic("/js/login.js"))
|
||||
|
||||
body
|
||||
main.main
|
||||
.center-login-container
|
||||
h1 Welcome to the Matrix!
|
||||
form.login-form
|
||||
form.login-form(method="post" onsubmit="return false")#form
|
||||
.data-input
|
||||
.form-input-container
|
||||
label(for="login") Username
|
||||
input(type="text" name="login" autocomplete="username" required)#login
|
||||
label(for="username") Username
|
||||
input(type="text" name="username" autocomplete="username" placeholder="@username:server.tld" pattern="^@?[a-z0-9._=/-]+(?::[a-zA-Z0-9.:\\[\\]-]+)?$" required)#username
|
||||
|
||||
.form-input-container
|
||||
label(for="password") Password
|
||||
|
@ -22,7 +23,10 @@ html
|
|||
|
||||
.form-input-container
|
||||
label(for="homeserver") Homeserver
|
||||
input(type="text" name="homeserver" value="matrix.org" required)#homeserver
|
||||
input(type="text" name="homeserver" value="matrix.org" placeholder="matrix.org" required)#homeserver
|
||||
|
||||
#feedback
|
||||
|
||||
.form-input-container
|
||||
input(type="submit" value="Login")
|
||||
input(type="submit" value="Log in")#submit
|
||||
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
flex-flow: column
|
||||
justify-content: center
|
||||
align-items: center
|
||||
width: min(100vw, 30rem)
|
||||
width: min(100vw, 450px)
|
||||
padding: max(1rem,3vw) 2rem
|
||||
height: 27rem
|
||||
box-shadow: -2px 2px 10px c.$darkest
|
||||
margin: 8px
|
||||
box-shadow: 0px 2px 10px c.$darkest
|
||||
background-color: c.$darker
|
||||
border-radius: 5px
|
||||
|
||||
|
@ -34,23 +34,48 @@
|
|||
flex-direction: column
|
||||
margin: 1em 0
|
||||
|
||||
input
|
||||
.form-feedback
|
||||
width: 100%
|
||||
margin: -0.5em 0 -0.8em
|
||||
|
||||
@keyframes spin
|
||||
0%
|
||||
transform: rotate(0deg)
|
||||
100%
|
||||
transform: rotate(180deg)
|
||||
|
||||
.loading-icon
|
||||
display: inline-block
|
||||
background-color: #ccc
|
||||
width: 12px
|
||||
height: 12px
|
||||
margin-right: 6px
|
||||
animation: spin 0.7s infinite
|
||||
|
||||
input, button
|
||||
font-family: inherit
|
||||
font-size: 17px
|
||||
background-color: c.$mild
|
||||
color: #eee
|
||||
width: 100%
|
||||
height: 2.4rem
|
||||
border-radius: 5px
|
||||
box-sizing: border-box
|
||||
transition: background-color 1s, border-color 1s
|
||||
padding: 0px 1ch
|
||||
transition: background-color 0.15s ease-out, border-color 0.15s ease-out
|
||||
padding: 4px 9px
|
||||
border: 0px
|
||||
|
||||
input[type="text"],input[type="password"]
|
||||
border: 3px solid transparent
|
||||
margin: 0.4em 0px
|
||||
&:hover
|
||||
border-color: c.$mild
|
||||
|
||||
input[type="submit"]:hover
|
||||
background-color: c.$mild
|
||||
&:hover, &:focus
|
||||
border-color: c.$milder
|
||||
|
||||
button, input[type="submit"]
|
||||
padding: 7px
|
||||
|
||||
&:hover
|
||||
background-color: c.$milder
|
||||
|
||||
label
|
||||
font-size: 1.2em
|
||||
font-size: 18px
|
||||
|
|
Loading…
Reference in a new issue