tangled
alpha
login
or
join now
mackuba.eu
/
skythread
14
fork
atom
Thread viewer for Bluesky
14
fork
atom
overview
issues
pulls
pipelines
moved the two dialogs to App component
mackuba.eu
3 months ago
72da4ab2
a0d68060
+90
-150
11 changed files
expand all
collapse all
unified
split
index.html
src
App.svelte
components
AccountMenu.svelte
BiohazardDialog.svelte
DialogPanel.svelte
Dialogs.svelte
LoginDialog.svelte
posts
HiddenRepliesLink.svelte
PostFooter.svelte
skythread.ts
style.css
-3
index.html
···
29
29
</a>
30
30
</div>
31
31
32
32
-
<div id="login" class="dialog"></div>
33
33
-
<div id="biohazard_dialog" class="dialog"></div>
34
34
-
35
32
<script src="dist/skythread.js"></script>
36
33
37
34
<script>
+6
-3
src/App.svelte
···
2
2
import { account } from './models/account.svelte.js';
3
3
4
4
import AccountMenu from './components/AccountMenu.svelte';
5
5
+
import Dialogs, { showLoginDialog } from './components/Dialogs.svelte';
5
6
import HashtagPage from './pages/HashtagPage.svelte';
6
7
import HomeSearch from './components/HomeSearch.svelte';
7
8
import LikeStatsPage from './pages/LikeStatsPage.svelte';
8
8
-
import LoginDialog from './components/LoginDialog.svelte';
9
9
import LycanSearchPage from './pages/LycanSearchPage.svelte';
10
10
import NotificationsPage from './pages/NotificationsPage.svelte';
11
11
import PostingStatsPage from './pages/PostingStatsPage.svelte';
···
14
14
import TimelineSearchPage from './pages/TimelineSearchPage.svelte';
15
15
16
16
let { params }: { params: Record<string, string> } = $props();
17
17
+
18
18
+
if (params.page && !account.loggedIn) {
19
19
+
showLoginDialog({ showClose: false });
20
20
+
}
17
21
</script>
18
22
19
23
<AccountMenu />
24
24
+
<Dialogs />
20
25
21
26
{#if params.q}
22
27
<ThreadPage url={params.q} />
···
29
34
{:else if params.page}
30
35
{#if account.loggedIn}
31
36
{@render page(params.page)}
32
32
-
{:else}
33
33
-
<LoginDialog />
34
37
{/if}
35
38
{:else}
36
39
<HomeSearch />
+2
-2
src/components/AccountMenu.svelte
···
1
1
<script lang="ts">
2
2
-
import { showLoginDialog } from '../skythread.js';
2
2
+
import { showLoginDialog } from './Dialogs.svelte';
3
3
import { account } from '../models/account.svelte.js';
4
4
import { settings } from '../models/settings.svelte.js';
5
5
import { getBaseLocation } from '../router.js';
···
44
44
function showLoginScreen(e: Event) {
45
45
e.preventDefault();
46
46
47
47
-
showLoginDialog();
47
47
+
showLoginDialog({ showClose: true });
48
48
menuVisible = false;
49
49
}
50
50
+11
-2
src/components/BiohazardDialog.svelte
···
1
1
<script lang="ts">
2
2
import { settings } from '../models/settings.svelte.js';
3
3
+
import DialogPanel from './DialogPanel.svelte';
4
4
+
5
5
+
type Props = {
6
6
+
onConfirm?: () => void;
7
7
+
onReject?: () => void;
8
8
+
onClose?: () => void;
9
9
+
}
3
10
4
4
-
type Props = { onConfirm?: (() => void) | undefined, onClose?: (() => void) | undefined };
5
5
-
let { onConfirm = undefined, onClose = undefined }: Props = $props();
11
11
+
let { onConfirm = undefined, onReject = undefined, onClose = undefined }: Props = $props();
6
12
7
13
function showBiohazard(e: Event) {
8
14
e.preventDefault();
···
16
22
e.preventDefault();
17
23
settings.biohazardsEnabled = false;
18
24
25
25
+
onReject?.();
19
26
onClose?.();
20
27
}
21
28
</script>
22
29
30
30
+
<DialogPanel onClose={() => onClose?.()}>
23
31
<form method="get">
24
32
<i class="close fa-circle-xmark fa-regular" onclick={onClose}></i>
25
33
<h2>☣️ Infohazard Warning</h2>
···
33
41
<input type="submit" value="Nope, I'd rather not 🙈" onclick={hideBiohazard}>
34
42
</p>
35
43
</form>
44
44
+
</DialogPanel>
36
45
37
46
<style>
38
47
form {
+14
src/components/DialogPanel.svelte
···
1
1
+
<script lang="ts">
2
2
+
let { children, onClose = undefined }: { children: any, onClose?: () => void } = $props();
3
3
+
4
4
+
function onclick(e: Event) {
5
5
+
// close the dialog (if it's closable) on click on the overlay, but not on anything inside
6
6
+
if (e.target === e.currentTarget) {
7
7
+
onClose?.();
8
8
+
}
9
9
+
}
10
10
+
</script>
11
11
+
12
12
+
<div class="dialog" {onclick}>
13
13
+
{@render children()}
14
14
+
</div>
+30
src/components/Dialogs.svelte
···
1
1
+
<script module lang="ts">
2
2
+
import BiohazardDialog from './BiohazardDialog.svelte';
3
3
+
import LoginDialog from './LoginDialog.svelte';
4
4
+
5
5
+
let loginDisplayed = $state(false);
6
6
+
let loginWithClose = $state(false);
7
7
+
8
8
+
let biohazardDisplayed = $state(false);
9
9
+
let biohazardOnConfirm: (() => void) | undefined = $state(undefined);
10
10
+
11
11
+
export function showLoginDialog(opts: { showClose: boolean }) {
12
12
+
if (!loginDisplayed) {
13
13
+
loginDisplayed = true;
14
14
+
loginWithClose = opts.showClose;
15
15
+
}
16
16
+
}
17
17
+
18
18
+
export function showBiohazardDialog(onConfirm?: () => void) {
19
19
+
if (!biohazardDisplayed) {
20
20
+
biohazardDisplayed = true;
21
21
+
biohazardOnConfirm = onConfirm;
22
22
+
}
23
23
+
}
24
24
+
</script>
25
25
+
26
26
+
{#if loginDisplayed}
27
27
+
<LoginDialog onClose={() => loginDisplayed = false} showClose={loginWithClose} />
28
28
+
{:else if biohazardDisplayed}
29
29
+
<BiohazardDialog onClose={() => biohazardDisplayed = false} onConfirm={() => biohazardOnConfirm?.()} />
30
30
+
{/if}
+22
-10
src/components/LoginDialog.svelte
···
1
1
<script lang="ts">
2
2
-
import { submitLogin } from '../skythread.js';
3
2
import { APIError } from '../api.js';
3
3
+
import { account } from '../models/account.svelte.js';
4
4
+
import DialogPanel from './DialogPanel.svelte';
4
5
5
5
-
let { onClose = undefined }: { onClose?: (() => void) | undefined } = $props();
6
6
+
type Props = {
7
7
+
onLogin?: () => void;
8
8
+
onClose?: () => void;
9
9
+
showClose: boolean;
10
10
+
}
11
11
+
12
12
+
let { onClose = undefined, onLogin = undefined, showClose }: Props = $props();
6
13
7
14
let identifier: string = $state('');
8
15
let password: string = $state('');
···
11
18
let loginField: HTMLInputElement;
12
19
let passwordField: HTMLInputElement;
13
20
14
14
-
function toggleLoginInfo(e: Event) {
15
15
-
e.preventDefault();
16
16
-
loginInfoVisible = !loginInfoVisible;
21
21
+
function onOverlayClick() {
22
22
+
if (showClose && onClose) {
23
23
+
onClose();
24
24
+
}
17
25
}
18
26
19
19
-
function onCloseClick(e: Event) {
27
27
+
function toggleLoginInfo(e: Event) {
20
28
e.preventDefault();
21
21
-
onClose?.();
29
29
+
loginInfoVisible = !loginInfoVisible;
22
30
}
23
31
24
32
async function onsubmit(e: Event) {
···
29
37
passwordField.blur();
30
38
31
39
try {
32
32
-
await submitLogin(identifier.trim(), password.trim());
40
40
+
await account.logIn(identifier.trim(), password.trim());
41
41
+
onLogin?.();
42
42
+
onClose?.();
33
43
} catch (error) {
34
44
submitting = false;
35
45
showError(error);
···
47
57
}
48
58
</script>
49
59
60
60
+
<DialogPanel onClose={onOverlayClick}>
50
61
<form method="get" {onsubmit}>
51
51
-
{#if onClose}
52
52
-
<i class="close fa-circle-xmark fa-regular" onclick={onCloseClick}></i>
62
62
+
{#if showClose}
63
63
+
<i class="close fa-circle-xmark fa-regular" onclick={onClose}></i>
53
64
{/if}
54
65
55
66
<h2>🌤 Skythread</h2>
···
80
91
{/if}
81
92
</p>
82
93
</form>
94
94
+
</DialogPanel>
83
95
84
96
<style>
85
97
.cloudy {
+1
-1
src/components/posts/HiddenRepliesLink.svelte
···
1
1
<script lang="ts">
2
2
import { api } from '../../api.js';
3
3
-
import { showBiohazardDialog } from '../../skythread.js';
3
3
+
import { showBiohazardDialog } from '../Dialogs.svelte';
4
4
import { settings } from '../../models/settings.svelte.js';
5
5
import { parseThreadPost } from '../../models/posts.js';
6
6
import { linkToPostThread } from '../../router.js';
+2
-2
src/components/posts/PostFooter.svelte
···
3
3
import { getPostContext } from './PostComponent.svelte';
4
4
import { linkToPostThread, linkToQuotesPage } from '../../router.js';
5
5
import { account } from '../../models/account.svelte.js';
6
6
-
import { showLoginDialog } from '../../skythread.js';
6
6
+
import { showLoginDialog } from '../Dialogs.svelte';
7
7
import { showError } from '../../utils.js';
8
8
9
9
let { post, placement } = getPostContext();
···
20
20
} else if (account.loggedIn) {
21
21
await checkIfCanBeLiked();
22
22
} else {
23
23
-
showLoginDialog();
23
23
+
showLoginDialog({ showClose: true });
24
24
}
25
25
} catch (error) {
26
26
showError(error);
+1
-125
src/skythread.ts
···
1
1
import * as svelte from 'svelte';
2
2
-
import App from './App.svelte';
3
3
-
import BiohazardDialog from './components/BiohazardDialog.svelte';
4
4
-
import LoginDialog from './components/LoginDialog.svelte';
5
5
-
6
2
import { BlueskyAPI } from './api.js';
7
7
-
import { account } from './models/account.svelte.js';
8
3
import { parseURLParams } from './router.js';
9
9
-
10
10
-
let loginDialog: Record<string, any> | undefined;
11
11
-
let biohazardDialog: Record<string, any> | undefined;
12
12
-
4
4
+
import App from './App.svelte';
13
5
14
6
function init() {
15
15
-
for (let dialog of document.querySelectorAll('.dialog')) {
16
16
-
let close = dialog.querySelector('.close') as HTMLElement;
17
17
-
18
18
-
dialog.addEventListener('click', (e) => {
19
19
-
if (e.target === e.currentTarget && close && close.offsetHeight > 0) {
20
20
-
hideDialog(dialog);
21
21
-
} else {
22
22
-
e.stopPropagation();
23
23
-
}
24
24
-
});
25
25
-
26
26
-
close?.addEventListener('click', (e) => {
27
27
-
hideDialog(dialog);
28
28
-
});
29
29
-
}
30
30
-
31
31
-
parseQueryParams();
32
32
-
}
33
33
-
34
34
-
function parseQueryParams() {
35
7
let params = parseURLParams(location.search);
36
8
svelte.mount(App, { target: document.body, props: { params }});
37
9
}
38
10
39
39
-
function hideDialog(dialog) {
40
40
-
dialog.style.visibility = 'hidden';
41
41
-
dialog.classList.remove('expanded');
42
42
-
document.getElementById('thread')!.classList.remove('overlay');
43
43
-
44
44
-
for (let field of dialog.querySelectorAll('input[type=text]')) {
45
45
-
field.value = '';
46
46
-
}
47
47
-
}
48
48
-
49
49
-
function showLoginDialog(showClose: boolean = true) {
50
50
-
if (loginDialog) {
51
51
-
return;
52
52
-
}
53
53
-
54
54
-
let dialog = document.getElementById('login')!
55
55
-
56
56
-
let props = {
57
57
-
onClose: showClose ? hideLoginDialog : undefined
58
58
-
};
59
59
-
60
60
-
dialog.addEventListener('click', (e) => {
61
61
-
if (e.target === e.currentTarget && showClose) {
62
62
-
hideLoginDialog();
63
63
-
} else {
64
64
-
e.stopPropagation();
65
65
-
}
66
66
-
});
67
67
-
68
68
-
loginDialog = svelte.mount(LoginDialog, { target: dialog, props });
69
69
-
70
70
-
dialog.style.visibility = 'visible';
71
71
-
document.getElementById('thread')!.classList.add('overlay');
72
72
-
}
73
73
-
74
74
-
function hideLoginDialog() {
75
75
-
if (loginDialog) {
76
76
-
svelte.unmount(loginDialog);
77
77
-
loginDialog = undefined;
78
78
-
79
79
-
document.getElementById('login')!.style.visibility = 'hidden';
80
80
-
document.getElementById('thread')!.classList.remove('overlay');
81
81
-
}
82
82
-
}
83
83
-
84
84
-
function showBiohazardDialog(onConfirm?: () => void) {
85
85
-
if (biohazardDialog) {
86
86
-
return;
87
87
-
}
88
88
-
89
89
-
let dialog = document.getElementById('biohazard_dialog')!
90
90
-
91
91
-
dialog.addEventListener('click', (e) => {
92
92
-
if (e.target === e.currentTarget) {
93
93
-
hideBiohazardDialog();
94
94
-
} else {
95
95
-
e.stopPropagation();
96
96
-
}
97
97
-
});
98
98
-
99
99
-
biohazardDialog = svelte.mount(BiohazardDialog, {
100
100
-
target: dialog,
101
101
-
props: {
102
102
-
onConfirm: onConfirm,
103
103
-
onClose: hideBiohazardDialog
104
104
-
}
105
105
-
});
106
106
-
107
107
-
dialog.style.visibility = 'visible';
108
108
-
document.getElementById('thread')!.classList.add('overlay');
109
109
-
}
110
110
-
111
111
-
function hideBiohazardDialog() {
112
112
-
if (biohazardDialog) {
113
113
-
svelte.unmount(biohazardDialog);
114
114
-
biohazardDialog = undefined;
115
115
-
116
116
-
document.getElementById('biohazard_dialog')!.style.visibility = 'hidden';
117
117
-
document.getElementById('thread')!.classList.remove('overlay');
118
118
-
}
119
119
-
}
120
120
-
121
121
-
async function submitLogin(identifier: string, password: string) {
122
122
-
await account.logIn(identifier, password);
123
123
-
124
124
-
hideLoginDialog();
125
125
-
126
126
-
let params = new URLSearchParams(location.search);
127
127
-
let page = params.get('page');
128
128
-
if (page) {
129
129
-
openPage(page);
130
130
-
}
131
131
-
}
132
132
-
133
11
window.init = init;
134
12
window.BlueskyAPI = BlueskyAPI;
135
135
-
136
136
-
export { showLoginDialog, showBiohazardDialog, submitLogin };
+1
-2
style.css
···
141
141
}
142
142
143
143
.dialog {
144
144
-
visibility: hidden;
145
144
position: fixed;
146
145
top: 0;
147
146
bottom: 0;
···
237
236
padding-top: 1px;
238
237
}
239
238
240
240
-
#thread.overlay {
239
239
+
.dialog ~ #thread {
241
240
filter: blur(8px);
242
241
}
243
242