feat: domain setup

This commit is contained in:
Shibo Lyu 2025-04-27 18:55:31 +08:00
parent b3e16f2063
commit 833460d709
5 changed files with 75 additions and 30 deletions

View file

@ -0,0 +1,21 @@
<script lang="ts">
import { tw } from '$lib/tw';
import type { HTMLAttributes } from 'svelte/elements';
interface Props extends HTMLAttributes<HTMLDivElement> {
class?: string;
children?: import('svelte').Snippet;
}
let { children, class: classNames, ...rest }: Props = $props();
</script>
<div
class={tw(
'border-ss-secondary bg-sb-primary overflow-hidden rounded-lg border-[0.5px] shadow-xs',
classNames
)}
{...rest}
>
{@render children?.()}
</div>

View file

@ -1,4 +1,6 @@
<script lang="ts">
import Card from '../Card.svelte';
interface Props {
header?: import('svelte').Snippet | string;
children?: import('svelte').Snippet;
@ -18,11 +20,9 @@
{/if}
</h3>
{/if}
<div
class="divide-ss-secondary border-ss-secondary bg-sb-primary divide-y-[0.5px] overflow-hidden rounded-lg border-[0.5px] shadow-xs"
>
<Card class="divide-ss-secondary divide-y-[0.5px]">
{@render children?.()}
</div>
</Card>
{#if footer}
<div class="text-sf-tertiary mt-1 px-4 text-sm">
{#if typeof footer === 'string'}

View file

@ -9,6 +9,8 @@ export function idURLToUsername(idURL: string): string {
return url.host;
}
export const identityDescriptionFilePath = '/.well-known/blah/identity.json';
export type IDURLValidity =
| { valid: true }
| ({ valid: false } & (
@ -39,7 +41,7 @@ export async function validateIDURL(url: string, identity: BlahIdentity): Promis
const profileFileURL = (() => {
let url = idURL;
url.pathname = '/.well-known/blah/identity.json';
url.pathname = identityDescriptionFilePath;
return url.toString();
})();

View file

@ -1,5 +1,4 @@
<script lang="ts">
import Dialog from '$lib/components/Dialog.svelte';
import { GroupedListItem } from '$lib/components/GroupedList';
import { idURLToUsername, validateIDURL, type IDURLValidity } from '$lib/idURL';
import type { BlahIdentity } from '@blah-im/core/identity';

View file

@ -1,10 +1,10 @@
<script lang="ts">
import Button from '$lib/components/Button.svelte';
import Card from '$lib/components/Card.svelte';
import Dialog from '$lib/components/Dialog.svelte';
import { GroupedListContainer, GroupedListSection } from '$lib/components/GroupedList';
import GroupedListContent from '$lib/components/GroupedList/GroupedListContent.svelte';
import Link from '$lib/components/Link.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import { idURLToUsername } from '$lib/idURL';
import { identityDescriptionFilePath, idURLToUsername } from '$lib/idURL';
import type { BlahIdentity } from '@blah-im/core/identity';
interface Props {
@ -19,6 +19,26 @@
const profileDescriptionString = $derived(
JSON.stringify(identity.generateIdentityDescription(), null, 2)
);
let copied = $state(false);
let jsonFileBlobHref: string | null = $state(null);
const copyToClipboard = async () => {
await navigator.clipboard.writeText(profileDescriptionString);
copied = true;
setTimeout(() => (copied = false), 2000);
};
$effect.pre(() => {
if (identity) {
const blob = new Blob([profileDescriptionString], { type: 'application/json' });
jsonFileBlobHref = URL.createObjectURL(blob);
}
return () => {
if (jsonFileBlobHref) URL.revokeObjectURL(jsonFileBlobHref);
};
});
</script>
<Dialog bind:open class="flex h-2/3 flex-col">
@ -27,25 +47,28 @@
<Button variant="primary" onclick={() => (open = false)}>Done</Button>
</PageHeader>
<GroupedListContainer class="w-full grow overflow-x-auto">
<GroupedListSection>
{#snippet header()}
<div class="-me-4 flex min-w-0 items-end gap-2 text-base normal-case">
<p class="text-sf-primary">
For others to validate your domain as your username, put the content below at
<code>/.well-known/blah/profile.json</code>
under your domain.
</p>
<Button>Copy</Button>
</div>
{/snippet}
<GroupedListContent class="p-0">
<textarea
readonly
class="text-sf-primary block h-100 w-full resize-none overflow-x-auto px-4 py-3 font-mono"
value={profileDescriptionString}
></textarea>
</GroupedListContent>
</GroupedListSection>
</GroupedListContainer>
<div class="flex grow flex-col gap-3 p-3">
<p class="text-sf-primary px-4 text-sm">
For others to validate ownership of your domain, make text file below available as
<code>{identityDescriptionFilePath}</code>
under your domain, and make sure it allows any cross domain requests.
<Link href="/">Learn more...</Link>
</p>
<Card class="relative grow">
<textarea
readonly
class="text-sf-primary block h-full w-full resize-none overflow-x-auto px-4 py-3 font-mono text-sm"
value={profileDescriptionString}
></textarea>
<div class="absolute end-2 top-2">
<Button onclick={copyToClipboard}>
{#if copied}Copied!{:else}Copy{/if}
</Button>
{#if jsonFileBlobHref}
<Button href={jsonFileBlobHref} download="identity.json">Download</Button>
{/if}
</div>
</Card>
</div>
</Dialog>