Thread viewer for Bluesky
at master 141 lines 3.7 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 p.info a { 125 color: #888; 126 } 127 128 .cloudy { 129 color: hsl(210, 60%, 75%); 130 } 131 132 .info-box { 133 border-color: hsl(45, 100%, 45%); 134 background-color: hsl(50, 40%, 30%); 135 } 136 137 .info-box a { 138 color: hsl(45, 100%, 50%); 139 } 140 } 141</style>