WIP: Some visual improvements for login #20
					 7 changed files with 191 additions and 33 deletions
				
			
		
							
								
								
									
										15
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1095,6 +1095,12 @@
 | 
			
		|||
        "to-fast-properties": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@popperjs/core": {
 | 
			
		||||
      "version": "2.5.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.5.3.tgz",
 | 
			
		||||
      "integrity": "sha512-RFwCobxsvZ6j7twS7dHIZQZituMIDJJNHS/qY6iuthVebxS3zhRY+jaC2roEKiAYaVuTcGmX6Luc6YBcf6zJVg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "@types/color-name": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -3832,6 +3838,15 @@
 | 
			
		|||
        "process": "~0.11.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "tippy.js": {
 | 
			
		||||
      "version": "6.2.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.2.7.tgz",
 | 
			
		||||
      "integrity": "sha512-k+kWF9AJz5xLQHBi3K/XlmJiyu+p9gsCyc5qZhxxGaJWIW8SMjw1R+C7saUnP33IM8gUhDA2xX//ejRSwqR0tA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@popperjs/core": "^2.4.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "to-fast-properties": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@
 | 
			
		|||
    "jshint": "^2.12.0",
 | 
			
		||||
    "node-fetch": "^2.6.0",
 | 
			
		||||
    "pug": "^3.0.0",
 | 
			
		||||
    "sass": "^1.26.10"
 | 
			
		||||
    "sass": "^1.26.10",
 | 
			
		||||
    "tippy.js": "^6.2.7"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,36 +1,78 @@
 | 
			
		|||
const tippy = require("tippy.js");
 | 
			
		||||
const {q, ElemJS, ejs} = require("./basic.js")
 | 
			
		||||
const {S_RE_DOMAIN, S_RE_IPV6, S_RE_IPV4} = require("./re.js")
 | 
			
		||||
 | 
			
		||||
const password = q("#password")
 | 
			
		||||
const homeserver = q("#homeserver")
 | 
			
		||||
 | 
			
		||||
// A regex matching a lossy MXID
 | 
			
		||||
// Groups:
 | 
			
		||||
//  1: username/localpart
 | 
			
		||||
//	MAYBE WITH
 | 
			
		||||
//  2: hostname/serverpart
 | 
			
		||||
//		3: domain
 | 
			
		||||
//		OR
 | 
			
		||||
//		4: IP address
 | 
			
		||||
// 			5: IPv4 address
 | 
			
		||||
//			OR
 | 
			
		||||
//			6: IPv6 address
 | 
			
		||||
//		MAYBE WITH
 | 
			
		||||
//		7: port number
 | 
			
		||||
const RE_LOSSY_MXID = new RegExp(`^@?([a-z0-9._=-]+?)(?::((?:(${S_RE_DOMAIN})|((${S_RE_IPV4})|(\\[${S_RE_IPV6}])))(?::(\\d+))?))?$`)
 | 
			
		||||
 | 
			
		||||
window.RE_LOSSY_MXID = RE_LOSSY_MXID
 | 
			
		||||
 | 
			
		||||
class Username extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
	constructor(homeserver) {
 | 
			
		||||
		super(q("#username"))
 | 
			
		||||
 | 
			
		||||
		this.homeserver = homeserver;
 | 
			
		||||
 | 
			
		||||
		this.on("change", this.updateServer.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isValid() {
 | 
			
		||||
		return !!this.element.value.match(/^@?[a-z0-9._=\/-]+(?::[a-zA-Z0-9.:\[\]-]+)?$/)
 | 
			
		||||
		return !!this.element.value.match(RE_LOSSY_MXID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getUsername() {
 | 
			
		||||
		return this.element.value.match(/^@?([a-z0-9._=\/-]+)/)[1]
 | 
			
		||||
		return this.element.value.match(RE_LOSSY_MXID)[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getServer() {
 | 
			
		||||
		const server = this.element.value.match(/^@?[a-z0-9._=\?-]+:([a-zA-Z0-9.:\[\]-]+)$/)
 | 
			
		||||
		if (server && server[1]) return server[1]
 | 
			
		||||
		const server = this.element.value.match(RE_LOSSY_MXID)
 | 
			
		||||
		if (server && server[2]) return server[2]
 | 
			
		||||
		else return null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updateServer() {
 | 
			
		||||
		if (!this.isValid()) return
 | 
			
		||||
		if (this.getServer()) homeserver.value = this.getServer()
 | 
			
		||||
		if (this.getServer()) this.homeserver.suggest(this.getServer())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const username = new Username()
 | 
			
		||||
class Homeserver extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super(q("#homeserver"));
 | 
			
		||||
 | 
			
		||||
		this.tippy = tippy.default(q(".homeserver-question"), {
 | 
			
		||||
			content: q("#homeserver-popup-template").innerHTML,
 | 
			
		||||
			allowHTML: true,
 | 
			
		||||
			interactive: true,
 | 
			
		||||
			interactiveBorder: 10,
 | 
			
		||||
			trigger: "focus mouseenter",
 | 
			
		||||
			theme: "carbon",
 | 
			
		||||
			arrow: tippy.roundArrow,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	suggest(value) {
 | 
			
		||||
		this.element.placeholder = value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getServer() {
 | 
			
		||||
		return this.element.value || this.element.placeholder;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Feedback extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
| 
						 | 
				
			
			@ -60,13 +102,14 @@ class Feedback extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const feedback = new Feedback()
 | 
			
		||||
 | 
			
		||||
class Form extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
	constructor(username, feedback, homeserver) {
 | 
			
		||||
		super(q("#form"))
 | 
			
		||||
 | 
			
		||||
		this.processing = false
 | 
			
		||||
		this.username = username
 | 
			
		||||
		this.feedback = feedback
 | 
			
		||||
		this.homeserver = homeserver
 | 
			
		||||
 | 
			
		||||
		this.on("submit", this.submit.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -74,12 +117,12 @@ class Form extends ElemJS {
 | 
			
		|||
	async submit() {
 | 
			
		||||
		if (this.processing) return
 | 
			
		||||
		this.processing = true
 | 
			
		||||
		if (!username.isValid()) return this.cancel("Username is not valid.")
 | 
			
		||||
		if (!this.username.isValid()) return this.cancel("Username is not valid.")
 | 
			
		||||
 | 
			
		||||
		// Resolve homeserver address
 | 
			
		||||
		let domain
 | 
			
		||||
		try {
 | 
			
		||||
			domain = await this.findHomeserver(homeserver.value)
 | 
			
		||||
			domain = await this.findHomeserver(this.homeserver.getServer())
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			return this.cancel(e.message)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +133,7 @@ class Form extends ElemJS {
 | 
			
		|||
			method: "POST",
 | 
			
		||||
			body: JSON.stringify({
 | 
			
		||||
				type: "m.login.password",
 | 
			
		||||
				user: username.getUsername(),
 | 
			
		||||
				user: this.username.getUsername(),
 | 
			
		||||
				password: password.value
 | 
			
		||||
			})
 | 
			
		||||
		}).then(res => res.json())
 | 
			
		||||
| 
						 | 
				
			
			@ -153,15 +196,21 @@ class Form extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	status(message) {
 | 
			
		||||
		feedback.setLoading(true)
 | 
			
		||||
		feedback.message(message)
 | 
			
		||||
		this.feedback.setLoading(true)
 | 
			
		||||
		this.feedback.message(message)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cancel(message) {
 | 
			
		||||
		this.processing = false
 | 
			
		||||
		feedback.setLoading(false)
 | 
			
		||||
		feedback.message(message, true)
 | 
			
		||||
		this.feedback.setLoading(false)
 | 
			
		||||
		this.feedback.message(message, true)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const form = new Form()
 | 
			
		||||
const homeserver = new Homeserver()
 | 
			
		||||
 | 
			
		||||
const username = new Username(homeserver)
 | 
			
		||||
 | 
			
		||||
const feedback = new Feedback()
 | 
			
		||||
 | 
			
		||||
const form = new Form(username, feedback, homeserver)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								src/js/re.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/js/re.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
// A valid internet domain, according to https://stackoverflow.com/a/20046959 (cleaned)
 | 
			
		||||
const S_RE_DOMAIN = "(?:[a-zA-Z]|[a-zA-Z][a-zA-Z]|[a-zA-Z]\\d|\\d[a-zA-Z]|[a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9])\\.(?:[a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\\.[a-zA-Z]{2,3})"
 | 
			
		||||
 | 
			
		||||
// A valid ipv4 address, one that doesn't check for valid numbers (e.g. not 999) and one that does
 | 
			
		||||
// const S_RE_IPV4_NO_CHECK = "(?:(?:\\d{1,3}\\.){3}\\d{1,3})"
 | 
			
		||||
const S_RE_IPV4_HAS_CHECK = "(?:(?:25[0-5]|(?:2[0-4]|1{0,1}\\d){0,1}\\d)\\.){3}(?:25[0-5]|(?:2[0-4]|1{0,1}\\d){0,1}\\d)"
 | 
			
		||||
 | 
			
		||||
const S_RE_IPV6_SEG = "[a-fA-F\\d]{1,4}"
 | 
			
		||||
// Yes, this is an ipv6 address.
 | 
			
		||||
const S_RE_IPV6 = `
 | 
			
		||||
(?:
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){7}(?:${S_RE_IPV6_SEG}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){6}(?:${S_RE_IPV4_HAS_CHECK}|:${S_RE_IPV6_SEG}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){5}(?::${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,2}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){4}(?:(?::${S_RE_IPV6_SEG}){0,1}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,3}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){3}(?:(?::${S_RE_IPV6_SEG}){0,2}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,4}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){2}(?:(?::${S_RE_IPV6_SEG}){0,3}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,5}|:)|
 | 
			
		||||
(?:${S_RE_IPV6_SEG}:){1}(?:(?::${S_RE_IPV6_SEG}){0,4}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,6}|:)|
 | 
			
		||||
(?::(?:(?::${S_RE_IPV6_SEG}){0,5}:${S_RE_IPV4_HAS_CHECK}|(?::${S_RE_IPV6_SEG}){1,7}|:))
 | 
			
		||||
)(?:%[0-9a-zA-Z]{1,})?`
 | 
			
		||||
  .replace(/\s*\/\/.*$/gm, '')
 | 
			
		||||
  .replace(/\n/g, '')
 | 
			
		||||
  .trim();
 | 
			
		||||
 | 
			
		||||
const RE_DOMAIN_EXACT = new RegExp(`^${S_RE_DOMAIN}$`)
 | 
			
		||||
const RE_IPV4_EXACT = new RegExp(`^${S_RE_IPV4_HAS_CHECK}$`)
 | 
			
		||||
const RE_IPV6_EXACT = new RegExp(`^${S_RE_IPV6}$`)
 | 
			
		||||
const RE_IP_ADDR_EXACT = new RegExp(`^${S_RE_IPV6}|${S_RE_IPV4_HAS_CHECK}$`)
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  S_RE_DOMAIN,
 | 
			
		||||
  S_RE_IPV6,
 | 
			
		||||
  S_RE_IPV4: S_RE_IPV4_HAS_CHECK,
 | 
			
		||||
  RE_DOMAIN_EXACT,
 | 
			
		||||
  RE_IPV4_EXACT,
 | 
			
		||||
  RE_IPV6_EXACT,
 | 
			
		||||
  RE_IP_ADDR_EXACT
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -15,18 +15,28 @@ html
 | 
			
		|||
          .data-input
 | 
			
		||||
            .form-input-container
 | 
			
		||||
              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
 | 
			
		||||
              input(type="text" name="username" autocomplete="username" placeholder="@username:server.com" pattern="^@?[a-z0-9._=/-]+(?::[a-zA-Z0-9.:\\[\\]-]+)?$" required)#username
 | 
			
		||||
 | 
			
		||||
            .form-input-container
 | 
			
		||||
              label(for="password") Password
 | 
			
		||||
              input(name="password" autocomplete="current-password" type="password" required)#password
 | 
			
		||||
 | 
			
		||||
            .form-input-container
 | 
			
		||||
              .homeserver-label
 | 
			
		||||
                label(for="homeserver") Homeserver
 | 
			
		||||
              input(type="text" name="homeserver" value="matrix.org" placeholder="matrix.org" required)#homeserver
 | 
			
		||||
                span.homeserver-question(tabindex=0) (What's this?)
 | 
			
		||||
              input(type="text" name="homeserver" placeholder="matrix.org")#homeserver
 | 
			
		||||
 | 
			
		||||
          #feedback
 | 
			
		||||
 | 
			
		||||
          .form-input-container
 | 
			
		||||
            input(type="submit" value="Log in")#submit
 | 
			
		||||
    template#homeserver-popup-template
 | 
			
		||||
        .homeserver-popup
 | 
			
		||||
           p
 | 
			
		||||
             | Homeserver is the place where your account lives.
 | 
			
		||||
             | It's usually the website where you registered.
 | 
			
		||||
           p
 | 
			
		||||
             | Need help finding a homeserver? 
 | 
			
		||||
             a(href='#')  Click here 
 | 
			
		||||
| 
					
	
 | 
			||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
@use "./base"
 | 
			
		||||
@use "./loading.sass"
 | 
			
		||||
@use "./colors.sass" as c
 | 
			
		||||
@use "./loading"
 | 
			
		||||
@use "./colors" as c
 | 
			
		||||
@use "./tippy"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.main
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +44,39 @@
 | 
			
		|||
.form-error
 | 
			
		||||
  color: red
 | 
			
		||||
| 
					
	
 
				
					
						cadence
						commented  
			
		This needs to be a lighter red for better contrast. Try #ff6561 I guess? This needs to be a lighter red for better contrast. Try #ff6561 I guess? 
			
			
		
				
					
						bad
						commented  
			
		I think we should get a color scheme first and then replace it with css(or sass) variables I think we should get a color scheme first and then replace it with css(or sass) variables
https://gitdab.com/cadence/Carbon/issues/27 
			
			
		 | 
			||||
 | 
			
		||||
.homeserver-question
 | 
			
		||||
| 
					
	
 
				
					
						cadence
						commented  
			
		Please create a class  Please create a class `.homeserver-question` and target that instead. Using IDs in CSS sounds fine but quickly turns into hell. I know this from experience. 
			
			
		 | 
			||||
  font-size: 0.8em
 | 
			
		||||
| 
					
	
 
				
					
						cadence
						commented  
			
		12px please, it doesn't need to be tiny. 12px please, it doesn't need to be _tiny._ 
			
			
		
				
					
						bad
						commented  
			
		Won't using px cause scaling issues on smaller screens? Won't using px cause scaling issues on smaller screens? 
			
			
		
				
					
						cadence
						commented  
			
		No, px will not cause issues. No, px will not cause issues. 
			
			
		 | 
			||||
 | 
			
		||||
| 
					
	
 
				
				bad marked this conversation as resolved
				
			 
			
				
					
						cadence
						commented  
			
		This does nothing, remove it. This does nothing, remove it. 
			
			
		 | 
			||||
.homeserver-label
 | 
			
		||||
  display: flex
 | 
			
		||||
  justify-content: space-between
 | 
			
		||||
  align-items: flex-end
 | 
			
		||||
 | 
			
		||||
.homeserver-popup
 | 
			
		||||
  p 
 | 
			
		||||
    margin: 0.2em
 | 
			
		||||
  a
 | 
			
		||||
    &, &:hover, &:active
 | 
			
		||||
      color: white
 | 
			
		||||
      text-decoration: none
 | 
			
		||||
    &:hover
 | 
			
		||||
      color: #f00 //Placeholder 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +101,7 @@ button, input[type="submit"]
 | 
			
		|||
  padding: 7px
 | 
			
		||||
 | 
			
		||||
  &:hover
 | 
			
		||||
    cursor: pointer
 | 
			
		||||
    background-color: c.$milder
 | 
			
		||||
 | 
			
		||||
label
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								src/sass/tippy.sass
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/sass/tippy.sass
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
@use "../../node_modules/tippy.js/dist/tippy.css"
 | 
			
		||||
@use "../../node_modules/tippy.js/dist/svg-arrow.css"
 | 
			
		||||
@use "./colors.sass" as c
 | 
			
		||||
 | 
			
		||||
.tippy-box[data-theme~="carbon"] 
 | 
			
		||||
  background-color: c.$milder
 | 
			
		||||
  border: 2px solid c.$divider 
 | 
			
		||||
 | 
			
		||||
| 
					
	
 
				
				bad marked this conversation as resolved
				
					
					
						Outdated
					
				
			 
			
				
					
						cadence
						commented  
			
		style: indentation 2 spaces, line break above selectors. style: indentation 2 spaces, line break above selectors. 
			
			
		 | 
			||||
  .tippy-svg-arrow
 | 
			
		||||
    fill: c.$milder
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	
This link doesn't go anywhere. I assume the site is yet to be created?
Can we make the link look like a link, aka underlined and blue?
Yeah right now we are waiting if someone in the matrix community will make a server picker similiar to joinmastodon.org.
https://github.com/daydream-mx/keymaker looks interesting but if it doesn't get finished we might have to make our own alternative