Thread viewer for Bluesky

moved the two dialogs to App component

+90 -150
-3
index.html
··· 29 29 </a> 30 30 </div> 31 31 32 - <div id="login" class="dialog"></div> 33 - <div id="biohazard_dialog" class="dialog"></div> 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 + 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 - 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 + 18 + if (params.page && !account.loggedIn) { 19 + showLoginDialog({ showClose: false }); 20 + } 17 21 </script> 18 22 19 23 <AccountMenu /> 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 - {:else} 33 - <LoginDialog /> 34 37 {/if} 35 38 {:else} 36 39 <HomeSearch />
+2 -2
src/components/AccountMenu.svelte
··· 1 1 <script lang="ts"> 2 - import { showLoginDialog } from '../skythread.js'; 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 - showLoginDialog(); 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 + import DialogPanel from './DialogPanel.svelte'; 4 + 5 + type Props = { 6 + onConfirm?: () => void; 7 + onReject?: () => void; 8 + onClose?: () => void; 9 + } 3 10 4 - type Props = { onConfirm?: (() => void) | undefined, onClose?: (() => void) | undefined }; 5 - let { onConfirm = undefined, onClose = undefined }: Props = $props(); 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 + onReject?.(); 19 26 onClose?.(); 20 27 } 21 28 </script> 22 29 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 + </DialogPanel> 36 45 37 46 <style> 38 47 form {
+14
src/components/DialogPanel.svelte
··· 1 + <script lang="ts"> 2 + let { children, onClose = undefined }: { children: any, onClose?: () => void } = $props(); 3 + 4 + function onclick(e: Event) { 5 + // close the dialog (if it's closable) on click on the overlay, but not on anything inside 6 + if (e.target === e.currentTarget) { 7 + onClose?.(); 8 + } 9 + } 10 + </script> 11 + 12 + <div class="dialog" {onclick}> 13 + {@render children()} 14 + </div>
+30
src/components/Dialogs.svelte
··· 1 + <script module lang="ts"> 2 + import BiohazardDialog from './BiohazardDialog.svelte'; 3 + import LoginDialog from './LoginDialog.svelte'; 4 + 5 + let loginDisplayed = $state(false); 6 + let loginWithClose = $state(false); 7 + 8 + let biohazardDisplayed = $state(false); 9 + let biohazardOnConfirm: (() => void) | undefined = $state(undefined); 10 + 11 + export function showLoginDialog(opts: { showClose: boolean }) { 12 + if (!loginDisplayed) { 13 + loginDisplayed = true; 14 + loginWithClose = opts.showClose; 15 + } 16 + } 17 + 18 + export function showBiohazardDialog(onConfirm?: () => void) { 19 + if (!biohazardDisplayed) { 20 + biohazardDisplayed = true; 21 + biohazardOnConfirm = onConfirm; 22 + } 23 + } 24 + </script> 25 + 26 + {#if loginDisplayed} 27 + <LoginDialog onClose={() => loginDisplayed = false} showClose={loginWithClose} /> 28 + {:else if biohazardDisplayed} 29 + <BiohazardDialog onClose={() => biohazardDisplayed = false} onConfirm={() => biohazardOnConfirm?.()} /> 30 + {/if}
+22 -10
src/components/LoginDialog.svelte
··· 1 1 <script lang="ts"> 2 - import { submitLogin } from '../skythread.js'; 3 2 import { APIError } from '../api.js'; 3 + import { account } from '../models/account.svelte.js'; 4 + import DialogPanel from './DialogPanel.svelte'; 4 5 5 - let { onClose = undefined }: { onClose?: (() => void) | undefined } = $props(); 6 + type Props = { 7 + onLogin?: () => void; 8 + onClose?: () => void; 9 + showClose: boolean; 10 + } 11 + 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 - function toggleLoginInfo(e: Event) { 15 - e.preventDefault(); 16 - loginInfoVisible = !loginInfoVisible; 21 + function onOverlayClick() { 22 + if (showClose && onClose) { 23 + onClose(); 24 + } 17 25 } 18 26 19 - function onCloseClick(e: Event) { 27 + function toggleLoginInfo(e: Event) { 20 28 e.preventDefault(); 21 - onClose?.(); 29 + loginInfoVisible = !loginInfoVisible; 22 30 } 23 31 24 32 async function onsubmit(e: Event) { ··· 29 37 passwordField.blur(); 30 38 31 39 try { 32 - await submitLogin(identifier.trim(), password.trim()); 40 + await account.logIn(identifier.trim(), password.trim()); 41 + onLogin?.(); 42 + onClose?.(); 33 43 } catch (error) { 34 44 submitting = false; 35 45 showError(error); ··· 47 57 } 48 58 </script> 49 59 60 + <DialogPanel onClose={onOverlayClick}> 50 61 <form method="get" {onsubmit}> 51 - {#if onClose} 52 - <i class="close fa-circle-xmark fa-regular" onclick={onCloseClick}></i> 62 + {#if showClose} 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 + </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 - import { showBiohazardDialog } from '../../skythread.js'; 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 - import { showLoginDialog } from '../../skythread.js'; 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 - showLoginDialog(); 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 - import App from './App.svelte'; 3 - import BiohazardDialog from './components/BiohazardDialog.svelte'; 4 - import LoginDialog from './components/LoginDialog.svelte'; 5 - 6 2 import { BlueskyAPI } from './api.js'; 7 - import { account } from './models/account.svelte.js'; 8 3 import { parseURLParams } from './router.js'; 9 - 10 - let loginDialog: Record<string, any> | undefined; 11 - let biohazardDialog: Record<string, any> | undefined; 12 - 4 + import App from './App.svelte'; 13 5 14 6 function init() { 15 - for (let dialog of document.querySelectorAll('.dialog')) { 16 - let close = dialog.querySelector('.close') as HTMLElement; 17 - 18 - dialog.addEventListener('click', (e) => { 19 - if (e.target === e.currentTarget && close && close.offsetHeight > 0) { 20 - hideDialog(dialog); 21 - } else { 22 - e.stopPropagation(); 23 - } 24 - }); 25 - 26 - close?.addEventListener('click', (e) => { 27 - hideDialog(dialog); 28 - }); 29 - } 30 - 31 - parseQueryParams(); 32 - } 33 - 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 - function hideDialog(dialog) { 40 - dialog.style.visibility = 'hidden'; 41 - dialog.classList.remove('expanded'); 42 - document.getElementById('thread')!.classList.remove('overlay'); 43 - 44 - for (let field of dialog.querySelectorAll('input[type=text]')) { 45 - field.value = ''; 46 - } 47 - } 48 - 49 - function showLoginDialog(showClose: boolean = true) { 50 - if (loginDialog) { 51 - return; 52 - } 53 - 54 - let dialog = document.getElementById('login')! 55 - 56 - let props = { 57 - onClose: showClose ? hideLoginDialog : undefined 58 - }; 59 - 60 - dialog.addEventListener('click', (e) => { 61 - if (e.target === e.currentTarget && showClose) { 62 - hideLoginDialog(); 63 - } else { 64 - e.stopPropagation(); 65 - } 66 - }); 67 - 68 - loginDialog = svelte.mount(LoginDialog, { target: dialog, props }); 69 - 70 - dialog.style.visibility = 'visible'; 71 - document.getElementById('thread')!.classList.add('overlay'); 72 - } 73 - 74 - function hideLoginDialog() { 75 - if (loginDialog) { 76 - svelte.unmount(loginDialog); 77 - loginDialog = undefined; 78 - 79 - document.getElementById('login')!.style.visibility = 'hidden'; 80 - document.getElementById('thread')!.classList.remove('overlay'); 81 - } 82 - } 83 - 84 - function showBiohazardDialog(onConfirm?: () => void) { 85 - if (biohazardDialog) { 86 - return; 87 - } 88 - 89 - let dialog = document.getElementById('biohazard_dialog')! 90 - 91 - dialog.addEventListener('click', (e) => { 92 - if (e.target === e.currentTarget) { 93 - hideBiohazardDialog(); 94 - } else { 95 - e.stopPropagation(); 96 - } 97 - }); 98 - 99 - biohazardDialog = svelte.mount(BiohazardDialog, { 100 - target: dialog, 101 - props: { 102 - onConfirm: onConfirm, 103 - onClose: hideBiohazardDialog 104 - } 105 - }); 106 - 107 - dialog.style.visibility = 'visible'; 108 - document.getElementById('thread')!.classList.add('overlay'); 109 - } 110 - 111 - function hideBiohazardDialog() { 112 - if (biohazardDialog) { 113 - svelte.unmount(biohazardDialog); 114 - biohazardDialog = undefined; 115 - 116 - document.getElementById('biohazard_dialog')!.style.visibility = 'hidden'; 117 - document.getElementById('thread')!.classList.remove('overlay'); 118 - } 119 - } 120 - 121 - async function submitLogin(identifier: string, password: string) { 122 - await account.logIn(identifier, password); 123 - 124 - hideLoginDialog(); 125 - 126 - let params = new URLSearchParams(location.search); 127 - let page = params.get('page'); 128 - if (page) { 129 - openPage(page); 130 - } 131 - } 132 - 133 11 window.init = init; 134 12 window.BlueskyAPI = BlueskyAPI; 135 - 136 - export { showLoginDialog, showBiohazardDialog, submitLogin };
+1 -2
style.css
··· 141 141 } 142 142 143 143 .dialog { 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 - #thread.overlay { 239 + .dialog ~ #thread { 241 240 filter: blur(8px); 242 241 } 243 242