Thread viewer for Bluesky
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="✱✱✱✱✱✱✱✱"
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>