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 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>