Thread viewer for Bluesky
at 2.0 174 lines 4.3 kB view raw
1<script lang="ts"> 2 import { APIError } from '../api.js'; 3 import { account } from '../models/account.svelte.js'; 4 import DialogPanel from './DialogPanel.svelte'; 5 6 type Props = { 7 onLogin?: () => void; 8 onClose?: () => void; 9 showClose: boolean; 10 } 11 12 let { onClose = undefined, onLogin = undefined, showClose }: Props = $props(); 13 14 let identifier: string = $state(''); 15 let password: string = $state(''); 16 let loginInfoVisible = $state(false); 17 let submitting = $state(false); 18 let loginField: HTMLInputElement; 19 let passwordField: HTMLInputElement; 20 21 function onOverlayClick() { 22 if (showClose && onClose) { 23 onClose(); 24 } 25 } 26 27 function toggleLoginInfo(e: Event) { 28 e.preventDefault(); 29 loginInfoVisible = !loginInfoVisible; 30 } 31 32 async function onsubmit(e: Event) { 33 e.preventDefault(); 34 submitting = true; 35 36 loginField.blur(); 37 passwordField.blur(); 38 39 try { 40 await account.logIn(identifier.trim(), password.trim()); 41 onLogin?.(); 42 onClose?.(); 43 } catch (error) { 44 submitting = false; 45 showError(error); 46 } 47 } 48 49 function showError(error: Error) { 50 console.log(error); 51 52 if (error instanceof APIError && error.code == 401 && error.json.error == 'AuthFactorTokenRequired') { 53 alert(`Please log in using an "app password" if you have 2FA enabled.`); 54 } else { 55 window.setTimeout(() => alert(error), 10); 56 } 57 } 58</script> 59 60<DialogPanel id="login" class={loginInfoVisible ? 'expanded' : ''} onClose={onOverlayClick}> 61 <form method="get" {onsubmit}> 62 {#if showClose} 63 <i class="close fa-circle-xmark fa-regular" onclick={onClose}></i> 64 {/if} 65 66 <h2>🌤 Skythread</h2> 67 68 <p><input type="text" id="login_handle" required autofocus placeholder="name.bsky.social" 69 bind:value={identifier} bind:this={loginField}></p> 70 71 <p><input type="password" id="login_password" required 72 placeholder="&#x2731;&#x2731;&#x2731;&#x2731;&#x2731;&#x2731;&#x2731;&#x2731;" 73 bind:value={password} bind:this={passwordField}></p> 74 75 <p class="info"> 76 <a href="#" onclick={toggleLoginInfo}><i class="fa-regular fa-circle-question"></i> Use an "app password" here</a> 77 </p> 78 79 {#if loginInfoVisible} 80 <div class="info-box"> 81 <p>Skythread doesn't support OAuth yet. For now, you need to use an "app password" here, which you can generate in the Bluesky app settings.</p> 82 <p>The password you enter here is only passed to the Bluesky API (PDS) and isn't saved anywhere. The returned access token is only stored in your browser's local storage. You can see the complete source code of this app <a href="http://tangled.org/mackuba.eu/skythread" target="_blank">on Tangled</a>.</p> 83 </div> 84 {/if} 85 86 <p class="submit"> 87 {#if !submitting} 88 <input type="submit" value="Log in"> 89 {:else} 90 <i class="cloudy fa-solid fa-cloud fa-beat fa-xl"></i> 91 {/if} 92 </p> 93 </form> 94</DialogPanel> 95 96<style> 97 p.info { 98 font-size: 9pt; 99 } 100 101 p.info a { 102 color: #666; 103 } 104 105 .cloudy { 106 color: hsl(210, 60%, 75%); 107 margin: 14px 0px; 108 } 109 110 .info-box { 111 border: 1px solid hsl(45, 100%, 60%); 112 background-color: hsl(50, 100%, 96%); 113 width: 360px; 114 font-size: 11pt; 115 border-radius: 6px; 116 } 117 118 .info-box p { 119 margin: 15px 15px; 120 text-align: left; 121 } 122 123 @media (prefers-color-scheme: dark) { 124 :global(#login) { 125 background-color: rgba(240, 240, 240, 0.15); 126 } 127 128 form { 129 border-color: hsl(210, 20%, 40%); 130 background-color: hsl(210, 12%, 25%); 131 } 132 133 .close { 134 color: hsl(210, 20%, 50%); 135 opacity: 0.6; 136 } 137 138 .close:hover { 139 color: hsl(210, 20%, 50%); 140 opacity: 1.0; 141 } 142 143 p.info a { 144 color: #888; 145 } 146 147 input[type="text"], input[type="password"] { 148 border-color: #666; 149 } 150 151 input[type="submit"] { 152 border-color: hsl(210, 15%, 40%); 153 background-color: hsl(210, 12%, 35%); 154 } 155 156 input[type="submit"]:active { 157 border-color: hsl(210, 15%, 35%); 158 background-color: hsl(210, 12%, 30%); 159 } 160 161 .cloudy { 162 color: hsl(210, 60%, 75%); 163 } 164 165 .info-box { 166 border-color: hsl(45, 100%, 45%); 167 background-color: hsl(50, 40%, 30%); 168 } 169 170 .info-box a { 171 color: hsl(45, 100%, 50%); 172 } 173 } 174</style>