mirror of
https://github.com/Blah-IM/Weblah.git
synced 2025-07-08 23:15:33 +00:00
refactor: migrate to svelte 5, vite 6 and bits-ui 1.
This commit is contained in:
parent
0bb201636a
commit
1e95dc0830
45 changed files with 1069 additions and 793 deletions
|
@ -1,8 +1,13 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { onNavigate } from '$app/navigation';
|
||||
import ChatList from './ChatList.svelte';
|
||||
import SettingsList from './settings/SettingsList.svelte';
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
|
||||
onNavigate((navigation) => {
|
||||
if (!document.startViewTransition || navigation.from?.url.href === navigation.to?.url.href)
|
||||
|
@ -16,10 +21,10 @@
|
|||
});
|
||||
});
|
||||
|
||||
$: isSettings = $page.route.id?.startsWith('/(app)/settings') ?? true;
|
||||
$: mainVisible =
|
||||
!!$page.params.chatId ||
|
||||
(isSettings && !$page.route.id?.startsWith('/(app)/settings/_mobile_empty'));
|
||||
let isSettings = $derived($page.route.id?.startsWith('/(app)/settings') ?? true);
|
||||
let mainVisible =
|
||||
$derived(!!$page.params.chatId ||
|
||||
(isSettings && !$page.route.id?.startsWith('/(app)/settings/_mobile_empty')));
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -38,10 +43,10 @@
|
|||
<main
|
||||
class="absolute inset-0 w-full bg-sb-secondary shadow-lg [view-transition-name:main] sm:relative sm:flex-1 sm:shadow-none"
|
||||
>
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</main>
|
||||
{:else}
|
||||
<div class="hidden flex-1 sm:block"><slot /></div>
|
||||
<div class="hidden flex-1 sm:block">{@render children?.()}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
const chatList = browser ? useChatList(chatServerConnectionPool.chatList) : null;
|
||||
|
||||
let isSearchFocused: boolean;
|
||||
let searchQuery: string;
|
||||
let isSearchFocused: boolean = $state(false);
|
||||
let searchQuery: string = $state('');
|
||||
</script>
|
||||
|
||||
<div class="flex h-[100dvh] flex-col justify-stretch">
|
||||
|
@ -18,14 +18,14 @@
|
|||
<div class="relative min-h-0 flex-1 touch-pan-y overflow-y-auto">
|
||||
<ul>
|
||||
{#if $chatList}
|
||||
{#each $chatList as chat}
|
||||
{#each $chatList as chat (chat.id)}
|
||||
<ChatListItem {chat} />
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
{#if isSearchFocused || searchQuery}
|
||||
<div
|
||||
class="absolute inset-0 size-full origin-top touch-pan-y overflow-y-auto bg-sb-primary"
|
||||
class="bg-sb-primary absolute inset-0 size-full origin-top touch-pan-y overflow-y-auto"
|
||||
transition:scale={{ start: 0.9 }}
|
||||
>
|
||||
<SearchPanel {searchQuery} />
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
import { tw } from '$lib/tw';
|
||||
import CurrentAccountPicture from './CurrentAccountPicture.svelte';
|
||||
|
||||
export let searchQuery: string = '';
|
||||
export let isSearchFocused: boolean;
|
||||
interface Props {
|
||||
searchQuery?: string;
|
||||
isSearchFocused: boolean;
|
||||
}
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
let { searchQuery = $bindable(''), isSearchFocused = $bindable() }: Props = $props();
|
||||
|
||||
let inputElement: HTMLInputElement = $state();
|
||||
|
||||
function onTapClear(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
|
@ -37,10 +41,10 @@
|
|||
class="w-full flex-1 bg-transparent text-sm leading-4 text-sf-primary focus:outline-hidden"
|
||||
bind:value={searchQuery}
|
||||
bind:this={inputElement}
|
||||
on:focus={() => {
|
||||
onfocus={() => {
|
||||
isSearchFocused = true;
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
onblur={(e) => {
|
||||
// If the related target is an anchor element, trigger the click as the user is trying to navigate
|
||||
if (
|
||||
e.relatedTarget instanceof HTMLAnchorElement ||
|
||||
|
@ -57,7 +61,7 @@
|
|||
'-mx-2 -my-1.5 flex size-8 cursor-text items-center justify-center text-slate-300 opacity-0 transition-[opacity,transform] duration-200 dark:text-slate-500',
|
||||
isSearchFocused && 'translate-x-full cursor-default opacity-100'
|
||||
)}
|
||||
on:click={onTapClear}
|
||||
onclick={onTapClear}
|
||||
>
|
||||
<Icon src={XCircle} class="size-4" mini />
|
||||
<span class="sr-only">Clear</span>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { AvatarBeam } from 'svelte-boring-avatars';
|
||||
|
||||
import { formatMessageDate, formatUnreadCount } from '$lib/formatters';
|
||||
|
@ -8,15 +10,19 @@
|
|||
import { page } from '$app/stores';
|
||||
import { tw } from '$lib/tw';
|
||||
|
||||
export let chat: Chat;
|
||||
|
||||
let urlSafeEndpoint: string;
|
||||
$: {
|
||||
const url = new URL(chat.server);
|
||||
urlSafeEndpoint = encodeURIComponent(url.hostname + url.pathname);
|
||||
interface Props {
|
||||
chat: Chat;
|
||||
}
|
||||
|
||||
$: isSelected = $page.params.chatId === chat.id;
|
||||
let { chat }: Props = $props();
|
||||
|
||||
let urlSafeEndpoint: string = $state();
|
||||
run(() => {
|
||||
const url = new URL(chat.server);
|
||||
urlSafeEndpoint = encodeURIComponent(url.hostname + url.pathname);
|
||||
});
|
||||
|
||||
let isSelected = $derived($page.params.chatId === chat.id);
|
||||
</script>
|
||||
|
||||
<li
|
||||
|
|
|
@ -7,9 +7,13 @@
|
|||
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let size: number = 32;
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
let accountStore: AccountStore;
|
||||
let { size = 32 }: Props = $props();
|
||||
|
||||
let accountStore: AccountStore = $state();
|
||||
|
||||
onMount(() => {
|
||||
openAccountStore().then((store) => {
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
|
||||
import ChatListItem from './ChatListItem.svelte';
|
||||
|
||||
export let name: string;
|
||||
export let results: Chat[];
|
||||
interface Props {
|
||||
name: string;
|
||||
results: Chat[];
|
||||
}
|
||||
|
||||
let { name, results }: Props = $props();
|
||||
</script>
|
||||
|
||||
<li>
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
import ChatListItem from './ChatListItem.svelte';
|
||||
import SearchChatResultSection from './SearchChatResultSection.svelte';
|
||||
|
||||
export let searchQuery: string;
|
||||
interface Props {
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
let { searchQuery }: Props = $props();
|
||||
|
||||
async function search(query: string) {
|
||||
const results = await chatServerConnectionPool.searchManager.searchChats(query);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { BlahChatServerConnection } from '$lib/blah/connection/chatServer';
|
||||
import { browser } from '$app/environment';
|
||||
|
@ -7,22 +9,22 @@
|
|||
import ChatPage from './ChatPage.svelte';
|
||||
import { useChat } from '$lib/chat';
|
||||
|
||||
$: roomId = $page.params.chatId;
|
||||
let roomId = $derived($page.params.chatId);
|
||||
|
||||
let serverEndpoint: string = '';
|
||||
$: {
|
||||
let serverEndpoint: string = $state('');
|
||||
run(() => {
|
||||
const endpointString = decodeURIComponent($page.params.server);
|
||||
serverEndpoint = endpointString.startsWith('http')
|
||||
? endpointString
|
||||
: `https://${endpointString}`;
|
||||
}
|
||||
});
|
||||
|
||||
let server: BlahChatServerConnection | null;
|
||||
$: {
|
||||
let server: BlahChatServerConnection | null = $state();
|
||||
run(() => {
|
||||
if (browser) {
|
||||
server = chatServerConnectionPool.getConnection(serverEndpoint);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full flex-col items-center justify-center">
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
import { AvatarBeam } from 'svelte-boring-avatars';
|
||||
import { ChevronLeft, Icon } from 'svelte-hero-icons';
|
||||
|
||||
export let info: Chat;
|
||||
export let outsideUnreadCount = 0;
|
||||
interface Props {
|
||||
info: Chat;
|
||||
outsideUnreadCount?: number;
|
||||
}
|
||||
|
||||
let { info, outsideUnreadCount = 0 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { VList } from 'virtua/svelte';
|
||||
|
||||
import ChatMessage from './ChatMessage.svelte';
|
||||
|
@ -9,46 +11,54 @@
|
|||
import ServiceMessage from '$lib/components/ServiceMessage.svelte';
|
||||
import { formatMessageSectionDate } from '$lib/formatters';
|
||||
|
||||
export let sectionedMessages: MessageSection[] = [];
|
||||
export let mySenderId: string;
|
||||
interface Props {
|
||||
sectionedMessages?: MessageSection[];
|
||||
mySenderId: string;
|
||||
}
|
||||
|
||||
let ref: VList<MessageSection> | undefined;
|
||||
let { sectionedMessages = [], mySenderId }: Props = $props();
|
||||
|
||||
let ref: VList<MessageSection> | undefined = $state();
|
||||
|
||||
async function scrollToIndex(index: number, smooth = true) {
|
||||
await tick();
|
||||
ref?.scrollToIndex(index, { align: 'end', smooth });
|
||||
}
|
||||
|
||||
$: scrollToIndex(sectionedMessages.length - 1);
|
||||
run(() => {
|
||||
scrollToIndex(sectionedMessages.length - 1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<VList data={sectionedMessages} let:item={messageSection} class="size-full pt-2" bind:this={ref}>
|
||||
{@const isMyself = mySenderId === messageSection.sender?.id}
|
||||
<VList data={sectionedMessages} class="size-full pt-2" bind:this={ref}>
|
||||
{#snippet children({ item: messageSection })}
|
||||
{@const isMyself = mySenderId === messageSection.sender?.id}
|
||||
|
||||
<div>
|
||||
{#if messageSection.date}
|
||||
<div class="pb-1.5 text-center">
|
||||
<ServiceMessage class="text-xs">
|
||||
{formatMessageSectionDate(messageSection.date)}
|
||||
</ServiceMessage>
|
||||
</div>
|
||||
{/if}
|
||||
<div class={tw('flex w-full items-end px-2', isMyself && 'flex-row-reverse')}>
|
||||
<div class="sticky bottom-1.5 mb-1.5 mt-1 w-8">
|
||||
<AvatarBeam size={32} name={messageSection.sender?.id} />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
{#if messageSection.sender && !isMyself}
|
||||
<div class="px-3 py-0.5 text-xs text-sf-tertiary">{messageSection.sender.name}</div>
|
||||
{/if}
|
||||
{#each messageSection.messages as message, idx}
|
||||
<ChatMessage
|
||||
{message}
|
||||
{isMyself}
|
||||
showBubbleTail={messageSection.messages.length - 1 === idx}
|
||||
/>
|
||||
{/each}
|
||||
<div>
|
||||
{#if messageSection.date}
|
||||
<div class="pb-1.5 text-center">
|
||||
<ServiceMessage class="text-xs">
|
||||
{formatMessageSectionDate(messageSection.date)}
|
||||
</ServiceMessage>
|
||||
</div>
|
||||
{/if}
|
||||
<div class={tw('flex w-full items-end px-2', isMyself && 'flex-row-reverse')}>
|
||||
<div class="sticky bottom-1.5 mb-1.5 mt-1 w-8">
|
||||
<AvatarBeam size={32} name={messageSection.sender?.id} />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
{#if messageSection.sender && !isMyself}
|
||||
<div class="px-3 py-0.5 text-xs text-sf-tertiary">{messageSection.sender.name}</div>
|
||||
{/if}
|
||||
{#each messageSection.messages as message, idx}
|
||||
<ChatMessage
|
||||
{message}
|
||||
{isMyself}
|
||||
showBubbleTail={messageSection.messages.length - 1 === idx}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</VList>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Delta, Editor } from 'typewriter-editor';
|
||||
|
||||
let editor: Editor | undefined;
|
||||
let delta: Delta;
|
||||
let plainText: string = '';
|
||||
let form: HTMLFormElement | null = null;
|
||||
let editor: Editor | undefined = $state();
|
||||
let delta: Delta | undefined = $state();
|
||||
let plainText: string = $state('');
|
||||
let form: HTMLFormElement | null = $state(null);
|
||||
|
||||
const dispatch = createEventDispatcher<{ sendMessage: BlahRichText }>();
|
||||
|
||||
|
@ -17,8 +17,10 @@
|
|||
form?.requestSubmit();
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (plainText.trim() === '') return;
|
||||
async function submit(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
if (plainText.trim() === '' || !delta) return;
|
||||
|
||||
const brt = deltaToBlahRichText(delta);
|
||||
dispatch('sendMessage', brt);
|
||||
|
@ -28,9 +30,9 @@
|
|||
</script>
|
||||
|
||||
<form
|
||||
class="flex w-full items-end gap-2 border-t border-ss-secondary bg-sb-primary p-2 shadow-xs"
|
||||
class="border-ss-secondary bg-sb-primary flex w-full items-end gap-2 border-t p-2 shadow-xs"
|
||||
bind:this={form}
|
||||
on:submit|preventDefault={submit}
|
||||
onsubmit={submit}
|
||||
>
|
||||
<Button class="p-1.5">
|
||||
<svg
|
||||
|
|
|
@ -3,9 +3,13 @@
|
|||
import { tw } from '$lib/tw';
|
||||
import type { Message } from '$lib/types';
|
||||
|
||||
export let message: Message;
|
||||
export let showBubbleTail: boolean = true;
|
||||
export let isMyself: boolean;
|
||||
interface Props {
|
||||
message: Message;
|
||||
showBubbleTail?: boolean;
|
||||
isMyself: boolean;
|
||||
}
|
||||
|
||||
let { message, showBubbleTail = true, isMyself }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={tw('mb-1.5 flex items-end gap-2 px-2', isMyself && 'flex-row-reverse')}>
|
||||
|
|
|
@ -11,8 +11,12 @@
|
|||
import type { BlahRichText } from '$lib/richText';
|
||||
import type { MessageSection } from '$lib/chat';
|
||||
|
||||
export let info: Readable<Chat>;
|
||||
export let sectionedMessages: Readable<MessageSection[]>;
|
||||
interface Props {
|
||||
info: Readable<Chat>;
|
||||
sectionedMessages: Readable<MessageSection[]>;
|
||||
}
|
||||
|
||||
let { info, sectionedMessages }: Props = $props();
|
||||
|
||||
interface $$Events {
|
||||
sendMessage: CustomEvent<BlahRichText>;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
import { flip } from 'svelte/animate';
|
||||
import { blur, scale } from 'svelte/transition';
|
||||
|
||||
let accountStore: AccountStore;
|
||||
let accountStore: AccountStore = $state();
|
||||
|
||||
onMount(() => {
|
||||
openAccountStore().then((store) => {
|
||||
|
|
|
@ -15,8 +15,12 @@
|
|||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import SettingsAccountSections from './SettingsAccountSections.svelte';
|
||||
|
||||
let className = '';
|
||||
export { className as class };
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { class: className = '' }: Props = $props();
|
||||
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
|
@ -3,13 +3,18 @@
|
|||
import { GroupedListItem } from '$lib/components/GroupedList';
|
||||
import { type IconSource } from 'svelte-hero-icons';
|
||||
|
||||
export let icon: IconSource | undefined = undefined;
|
||||
export let route: string | undefined = undefined;
|
||||
interface Props {
|
||||
icon?: IconSource | undefined;
|
||||
route?: string | undefined;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
$: selected = route
|
||||
let { icon = undefined, route = undefined, children }: Props = $props();
|
||||
|
||||
let selected = $derived(route
|
||||
? $page.route.id?.startsWith(`/(app)/settings${route}`)
|
||||
: $page.route.id === '/(app)/settings';
|
||||
$: href = `/settings${route}`;
|
||||
: $page.route.id === '/(app)/settings');
|
||||
let href = $derived(`/settings${route}`);
|
||||
</script>
|
||||
|
||||
<GroupedListItem {icon} {selected} {href}><slot /></GroupedListItem>
|
||||
<GroupedListItem {icon} {selected} {href}>{@render children?.()}</GroupedListItem>
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
<GroupedListContainer>
|
||||
<GroupedListSection>
|
||||
<p slot="footer">New here? <a class="text-accent-500" href="new">Create a new account</a>.</p>
|
||||
{#snippet footer()}
|
||||
<p >New here? <a class="text-accent-500" href="new">Create a new account</a>.</p>
|
||||
{/snippet}
|
||||
</GroupedListSection>
|
||||
</GroupedListContainer>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { currentAccountStore, openAccountStore } from '$lib/accounts/accountStore';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
|
@ -18,17 +18,17 @@
|
|||
import { onMount } from 'svelte';
|
||||
import type { Delta, Editor } from 'typewriter-editor';
|
||||
|
||||
let name: string = '';
|
||||
let editor: Editor | undefined;
|
||||
let delta: Delta;
|
||||
let plainText: string = '';
|
||||
let password: string = '';
|
||||
let repeatPassword: string = '';
|
||||
let identityServer: string = 'other.blue';
|
||||
let name: string = $state('');
|
||||
let editor: Editor | undefined = $state();
|
||||
let delta: Delta | undefined = $state();
|
||||
let plainText: string = $state('');
|
||||
let password: string = $state('');
|
||||
let repeatPassword: string = $state('');
|
||||
let identityServer: string = $state('other.blue');
|
||||
|
||||
let isBusy: boolean = false;
|
||||
let isBusy: boolean = $state(false);
|
||||
|
||||
let bioPlaceholder = 'Introduce yourself.';
|
||||
let bioPlaceholder = $state('Introduce yourself.');
|
||||
|
||||
const bioPlaceholders = [
|
||||
'a 23 yo. designer from Tokyo.',
|
||||
|
@ -38,9 +38,9 @@
|
|||
'a 28 yo. writer from London.'
|
||||
];
|
||||
|
||||
$: passwordMatch = password === repeatPassword;
|
||||
$: canCreate = name.length > 0 && password.length > 0 && passwordMatch;
|
||||
$: customize = $page.url.hash === '#customize';
|
||||
let passwordMatch = $derived(password === repeatPassword);
|
||||
let canCreate = $derived(name.length > 0 && password.length > 0 && passwordMatch);
|
||||
let customize = $derived(page.url.hash === '#customize');
|
||||
|
||||
onMount(() => {
|
||||
const bioPlaceholderRotateRef = setInterval(() => {
|
||||
|
@ -76,7 +76,7 @@
|
|||
{#if isBusy}
|
||||
<LoadingIndicator class="size-4" />
|
||||
{:else}
|
||||
<Button variant="primary" disabled={!canCreate} on:click={createAccount}>Create</Button>
|
||||
<Button variant="primary" disabled={!canCreate} onclick={createAccount}>Create</Button>
|
||||
{/if}
|
||||
</PageHeader>
|
||||
|
||||
|
@ -89,13 +89,15 @@
|
|||
bind:value={name}
|
||||
placeholder="Your Name"
|
||||
disabled={isBusy}
|
||||
class="ms-3 flex-1 bg-transparent text-lg leading-loose caret-accent-500 outline-hidden placeholder:opacity-50"
|
||||
class="caret-accent-500 ms-3 flex-1 bg-transparent text-lg leading-loose outline-hidden placeholder:opacity-50"
|
||||
/>
|
||||
</GroupedListItem>
|
||||
</GroupedListSection>
|
||||
|
||||
<GroupedListSection>
|
||||
<h4 slot="header">Bio</h4>
|
||||
{#snippet header()}
|
||||
<h4>Bio</h4>
|
||||
{/snippet}
|
||||
<RichTextInput
|
||||
class="p-4 shadow-none ring-0"
|
||||
bind:editor
|
||||
|
@ -103,11 +105,15 @@
|
|||
bind:plainText
|
||||
placeholder={bioPlaceholder}
|
||||
/>
|
||||
<p slot="footer">Introduce yourself. This will be public for everyone to see.</p>
|
||||
{#snippet footer()}
|
||||
<p>Introduce yourself. This will be public for everyone to see.</p>
|
||||
{/snippet}
|
||||
</GroupedListSection>
|
||||
|
||||
<GroupedListSection>
|
||||
<h4 slot="header">Security</h4>
|
||||
{#snippet header()}
|
||||
<h4>Security</h4>
|
||||
{/snippet}
|
||||
<GroupedListInputItem>
|
||||
Password
|
||||
<input type="password" bind:value={password} placeholder="Password" disabled={isBusy} />
|
||||
|
@ -121,33 +127,39 @@
|
|||
disabled={isBusy}
|
||||
/>
|
||||
</GroupedListInputItem>
|
||||
<div slot="footer" class="space-y-1">
|
||||
<p>
|
||||
Sensitive actions like signing in on new devices require your password. Make sure it's
|
||||
unique and secure. You'll lose access to your account if you forget it.
|
||||
</p>
|
||||
{#if !passwordMatch && repeatPassword}
|
||||
<p><strong>Passwords do not match.</strong></p>
|
||||
{/if}
|
||||
</div>
|
||||
{#snippet footer()}
|
||||
<div class="space-y-1">
|
||||
<p>
|
||||
Sensitive actions like signing in on new devices require your password. Make sure it's
|
||||
unique and secure. You'll lose access to your account if you forget it.
|
||||
</p>
|
||||
{#if !passwordMatch && repeatPassword}
|
||||
<p><strong>Passwords do not match.</strong></p>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</GroupedListSection>
|
||||
{#if customize}
|
||||
<GroupedListSection>
|
||||
<h4 slot="header">Identity Service</h4>
|
||||
{#snippet header()}
|
||||
<h4>Identity Service</h4>
|
||||
{/snippet}
|
||||
<GroupedListInputItem>
|
||||
Initial Service
|
||||
<input type="text" bind:value={identityServer} />
|
||||
</GroupedListInputItem>
|
||||
<div slot="footer" class="space-y-1">
|
||||
<p>
|
||||
Your profile is stored and served to other users on the identity service.
|
||||
<Link href="/" variant="secondary">Learn more about identity services...</Link>
|
||||
</p>
|
||||
<p>You can add, replace or remove identity services later in account settings.</p>
|
||||
</div>
|
||||
{#snippet footer()}
|
||||
<div class="space-y-1">
|
||||
<p>
|
||||
Your profile is stored and served to other users on the identity service.
|
||||
<Link href="/" variant="secondary">Learn more about identity services...</Link>
|
||||
</p>
|
||||
<p>You can add, replace or remove identity services later in account settings.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
</GroupedListSection>
|
||||
{/if}
|
||||
<div class="px-8 text-sm text-sf-tertiary">
|
||||
<div class="text-sf-tertiary px-8 text-sm">
|
||||
<p>
|
||||
By creating an account, you agree to Terms of Service and Privacy Policy of
|
||||
<em>{identityServer}</em>, which stores and serve your public profile to other users.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue