github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/view/login.html (about) 1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <base href="$$.RootPath$$"> 6 <title>Login - Shiori</title> 7 8 <meta charset="UTF-8"> 9 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 10 11 <link rel="apple-touch-icon-precomposed" sizes="152x152" href="assets/res/apple-touch-icon-152x152.png"> 12 <link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/res/apple-touch-icon-144x144.png"> 13 <link rel="icon" type="image/png" href="assets/res/favicon-32x32.png" sizes="32x32"> 14 <link rel="icon" type="image/png" href="assets/res/favicon-16x16.png" sizes="16x16"> 15 <link rel="icon" type="image/x-icon" href="assets/res/favicon.ico"> 16 17 <link href="assets/css/style.css" rel="stylesheet"> 18 19 <script src="assets/js/vue.min.js"></script> 20 <script src="assets/js/url.min.js"></script> 21 </head> 22 23 <body> 24 <div id="login-scene" :class="{night: NightMode}"> 25 <p class="error-message" v-if="error !== ''">{{error}}</p> 26 <div id="login-box"> 27 <form @submit.prevent="login"> 28 <div id="logo-area"> 29 <p id="logo"> 30 <span>栞</span>shiori 31 </p> 32 <p id="tagline">simple bookmark manager</p> 33 </div> 34 <div id="input-area"> 35 <label for="username">Username: </label> 36 <input id="username" type="text" name="username" placeholder="Username" tabindex="1" autofocus /> 37 <label for="password">Password: </label> 38 <input id="password" type="password" name="password" placeholder="Password" tabindex="2" 39 @keyup.enter="login"> 40 <label class="checkbox-field"><input type="checkbox" name="remember" v-model="remember" 41 tabindex="3">Remember me</label> 42 </div> 43 <div id="button-area"> 44 <a v-if="loading"> 45 <i class="fas fa-fw fa-spinner fa-spin"></i> 46 </a> 47 <a v-else class="button" tabindex="4" @click="login" @keyup.enter="login">Log In</a> 48 </div> 49 </form> 50 </div> 51 <div> 52 <p>$$.Version$$</p> 53 </div> 54 </div> 55 56 <script type="module"> 57 var app = new Vue({ 58 el: "#login-scene", 59 data: { 60 error: "", 61 loading: false, 62 username: "", 63 password: "", 64 remember: false, 65 NightMode: false, 66 }, 67 methods: { 68 async getErrorMessage(err) { 69 switch (err.constructor) { 70 case Error: 71 return err.message; 72 case Response: 73 var text = await err.text(); 74 75 // Handle new error messages 76 if (text[0] == "{") { 77 var json = JSON.parse(text); 78 return json.message; 79 } 80 return `${text} (${err.status})`; 81 default: 82 return err; 83 } 84 }, 85 login() { 86 function parseJWT(token) { 87 try { 88 return JSON.parse(atob(token.split('.')[1])); 89 } catch (e) { 90 return null; 91 } 92 } 93 94 // needed to work around autofill issue 95 // https://github.com/facebook/react/issues/1159#issuecomment-506584346 96 this.username = document.querySelector('#username').value; 97 this.password = document.querySelector('#password').value; 98 // Validate input 99 if (this.username === "") { 100 this.error = "Username must not empty"; 101 return; 102 } 103 104 // Remove old cookie 105 document.cookie = `session-id=; Path=${new URL(document.baseURI).pathname}; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`; 106 107 // Send request 108 this.loading = true; 109 110 fetch(new URL("api/v1/auth/login", document.baseURI), { 111 method: "post", 112 body: JSON.stringify({ 113 username: this.username, 114 password: this.password, 115 remember_me: this.remember == 1 ? true : false, 116 }), 117 headers: { "Content-Type": "application/json" }, 118 }).then(response => { 119 if (!response.ok) throw response; 120 return response.json(); 121 }).then(json => { 122 // Save session id 123 document.cookie = `session-id=${json.message.session}; Path=${new URL(document.baseURI).pathname}; Expires=${json.message.expires}`; 124 document.cookie = `token=${json.message.token}; Path=${new URL(document.baseURI).pathname}; Expires=${json.message.expires}`; 125 126 // Save account data 127 localStorage.setItem("shiori-token", json.message.token); 128 localStorage.setItem("shiori-account", JSON.stringify(parseJWT(json.message.token).account)); 129 130 // Go to destination page 131 var currentUrl = new Url(), 132 dstUrl = currentUrl.query.dst, 133 dstPage = currentUrl.hash || "home"; 134 135 if (dstPage !== "home" && dstPage !== "setting") { 136 dstPage = ""; 137 } 138 139 // TODO: Improve this redirect logic 140 var newUrl = new Url(dstUrl || document.baseURI); 141 newUrl.hash = dstPage; 142 143 location.href = newUrl; 144 }).catch(err => { 145 this.loading = false; 146 this.getErrorMessage(err).then(msg => { 147 this.error = msg; 148 }) 149 }); 150 }, 151 loadSetting() { 152 var opts = JSON.parse(localStorage.getItem("shiori-account")) || {}, 153 NightMode = (typeof opts.config.NightMode === "boolean") ? opts.configNightMode : false; 154 155 this.NightMode = NightMode; 156 } 157 }, 158 mounted() { 159 // Load setting 160 this.loadSetting(); 161 localStorage.removeItem("shiori-account"); 162 localStorage.removeItem("shiori-token"); 163 164 // <input autofocus> wasn't working all the time, so I'm putting this here as a fallback 165 document.querySelector('#username').focus() 166 } 167 }) 168 </script> 169 </body> 170 171 </html>