Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/Beta.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,3 @@
- when adding images try to add them in a size that best fits aspect ratio

- onboarding

- fix invalid handle thing
4 changes: 3 additions & 1 deletion src/lib/atproto/UI/LoginModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@
{#if showRecentLogins}
<div class="mt-2 mb-2 text-sm font-medium">Recent logins</div>
<div class="flex flex-col gap-2">
{#each Object.values(recentLogins).slice(0, 4) as recentLogin (recentLogin.did)}
{#each Object.values(recentLogins)
.filter((l) => l.handle && l.handle !== 'handle.invalid')
.slice(0, 4) as recentLogin (recentLogin.did)}
<div class="group">
<div
class="group-hover:bg-base-300 bg-base-200 dark:bg-base-700 dark:hover:bg-base-600 dark:border-base-500/50 border-base-300 relative flex h-10 w-full items-center justify-between gap-2 rounded-full border px-2 font-semibold transition-colors duration-100"
Expand Down
24 changes: 17 additions & 7 deletions src/lib/atproto/auth.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import { dev } from '$app/environment';
import { replaceState } from '$app/navigation';

import { metadata } from './metadata';
import { getDetailedProfile } from './methods';
import { signUpPDS } from './settings';
import { describeRepo, getDetailedProfile } from './methods';
import { DOH_RESOLVER, REDIRECT_PATH, signUpPDS } from './settings';
import { SvelteURLSearchParams } from 'svelte/reactivity';

import type { ActorIdentifier, Did } from '@atcute/lexicons';
Expand All @@ -42,21 +42,21 @@ export async function initClient() {

const clientId = dev
? `http://localhost` +
`?redirect_uri=${encodeURIComponent('http://127.0.0.1:5179/oauth/callback')}` +
`?redirect_uri=${encodeURIComponent('http://127.0.0.1:5179' + REDIRECT_PATH)}` +
`&scope=${encodeURIComponent(metadata.scope)}`
: metadata.client_id;

const handleResolver = new CompositeHandleResolver({
methods: {
dns: new DohJsonHandleResolver({ dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query' }),
dns: new DohJsonHandleResolver({ dohUrl: DOH_RESOLVER }),
http: new WellKnownHandleResolver()
}
});

configureOAuth({
metadata: {
client_id: clientId,
redirect_uri: dev ? 'http://127.0.0.1:5179/oauth/callback' : metadata.redirect_uris[0]
redirect_uri: dev ? 'http://127.0.0.1:5179' + REDIRECT_PATH : metadata.redirect_uris[0]
},
identityResolver: new LocalActorResolver({
handleResolver: handleResolver,
Expand Down Expand Up @@ -224,6 +224,16 @@ async function loadProfile(actor: Did) {

const response = await getDetailedProfile();

user.profile = response;
localStorage.setItem(`profile-${actor}`, JSON.stringify(response));
if (!response || response.handle === 'handle.invalid') {
console.log('invalid handle or no profile from bsky, fetching from repo description');
const repo = await describeRepo({ did: actor });
user.profile = {
did: actor,
handle: repo?.handle || 'handle.invalid'
};
localStorage.setItem(`profile-${actor}`, JSON.stringify(user.profile));
} else {
user.profile = response;
localStorage.setItem(`profile-${actor}`, JSON.stringify(response));
}
}
8 changes: 4 additions & 4 deletions src/lib/atproto/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolve } from '$app/paths';
import { permissions, SITE } from './settings';
import { permissions, REDIRECT_PATH, SITE } from './settings';

function constructScope() {
const repos = permissions.collections.map((collection) => 'repo:' + collection).join(' ');
Expand All @@ -14,9 +14,9 @@ function constructScope() {
}

let blobScope: string | undefined = undefined;
if (Array.isArray(permissions.blobs)) {
if (Array.isArray(permissions.blobs) && permissions.blobs.length > 0) {
blobScope = 'blob?' + permissions.blobs.map((b) => 'accept=' + b).join('&');
} else if (permissions.blobs) {
} else if (permissions.blobs && permissions.blobs.length > 0) {
blobScope = 'blob:' + permissions.blobs;
}

Expand All @@ -26,7 +26,7 @@ function constructScope() {

export const metadata = {
client_id: SITE + resolve('/oauth-client-metadata.json'),
redirect_uris: [SITE + resolve('/oauth/callback')],
redirect_uris: [SITE + resolve(REDIRECT_PATH)],
scope: constructScope(),
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
Expand Down
9 changes: 8 additions & 1 deletion src/lib/atproto/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,11 @@ export type AllowedCollection = ExtractCollectionBase<(typeof permissions.collec

// which PDS to use for signup
// ATTENTION: pds.rip is only for development, all accounts get deleted automatically after a week
export const signUpPDS = dev ? 'https://pds.rip/' : 'https://selfhosted.social';
const devPDS = 'https://pds.rip/';
const prodPDS = 'https://selfhosted.social/';
export const signUpPDS = dev ? devPDS : prodPDS;

// where to redirect after oauth login/signup, e.g. /oauth/callback
export const REDIRECT_PATH = '/oauth/callback';

export const DOH_RESOLVER = 'https://mozilla.cloudflare-dns.com/dns-query';
12 changes: 11 additions & 1 deletion src/lib/cards/FluidTextCard/EditingFluidTextCard.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import type { Item } from '$lib/types';
import { onMount, tick } from 'svelte';
import type { ContentComponentProps } from '../types';
import FluidTextCard from './FluidTextCard.svelte';

Expand All @@ -26,6 +27,15 @@
isEditing = false;
}
}

let rerender = $state(0);
onMount(() => {
window.addEventListener('theme-changed', async () => {
// Force re-render to update FluidTextCard colors
await tick();
rerender = Math.random();
});
});
</script>

<!-- svelte-ignore a11y_no_static_element_interactions -->
Expand All @@ -36,7 +46,7 @@
: ''}"
onclick={handleClick}
>
{#key item.color}
{#key item.color + '-' + rerender.toString()}
<FluidTextCard {item} />
{/key}

Expand Down
123 changes: 123 additions & 0 deletions src/lib/website/Controls.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<script lang="ts">
import { SelectThemePopover } from '$lib/components/select-theme';
import { getHideProfileSection, getProfilePosition } from '$lib/helper';
import type { WebsiteData } from '$lib/types';
import { Button } from '@foxui/core';
import { getIsMobile } from './context';

let { data = $bindable() }: { data: WebsiteData } = $props();

let accentColor = $derived(data.publication?.preferences?.accentColor ?? 'pink');
let baseColor = $derived(data.publication?.preferences?.baseColor ?? 'stone');

function updateTheme(newAccent: string, newBase: string) {
data.publication.preferences ??= {};
data.publication.preferences.accentColor = newAccent;
data.publication.preferences.baseColor = newBase;
data = { ...data };
}

let profilePosition = $derived(getProfilePosition(data));

function toggleProfilePosition() {
data.publication.preferences ??= {};
data.publication.preferences.profilePosition = profilePosition === 'side' ? 'top' : 'side';
data = { ...data };
}

let isMobile = getIsMobile();
</script>

<div class={['fixed top-2 left-14 z-20 flex gap-2']}>
<Button
size="icon"
onclick={() => {
data.publication.preferences ??= {};
data.publication.preferences.hideProfileSection =
!data.publication.preferences?.hideProfileSection;
data = { ...data };
}}
variant="ghost"
>
{#if !getHideProfileSection(data)}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-5!"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-5!"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
</svg>
{/if}
</Button>

<!-- Position toggle button (desktop only) -->
{#if !isMobile() && !getHideProfileSection(data)}
<Button size="icon" type="button" onclick={toggleProfilePosition} variant="ghost">
{#if profilePosition === 'side'}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-5!"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-5!"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m19.5 4.5-15 15m0 0h11.25m-11.25 0V8.25"
/>
</svg>
{/if}
</Button>
{/if}

<!-- Theme selection -->
<SelectThemePopover
{accentColor}
{baseColor}
onchanged={(newAccent, newBase) => updateTheme(newAccent, newBase)}
/>
</div>
96 changes: 1 addition & 95 deletions src/lib/website/EditableProfile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,6 @@
let { data = $bindable(), hideBlento = false }: { data: WebsiteData; hideBlento?: boolean } =
$props();

let accentColor = $derived(data.publication?.preferences?.accentColor ?? 'pink');
let baseColor = $derived(data.publication?.preferences?.baseColor ?? 'stone');

function updateTheme(newAccent: string, newBase: string) {
data.publication.preferences ??= {};
data.publication.preferences.accentColor = newAccent;
data.publication.preferences.baseColor = newBase;
data = { ...data };
}

let profilePosition = $derived(getProfilePosition(data));

function toggleProfilePosition() {
data.publication.preferences ??= {};
data.publication.preferences.profilePosition = profilePosition === 'side' ? 'top' : 'side';
data = { ...data };
}

let fileInput: HTMLInputElement;
let isHoveringAvatar = $state(false);

Expand Down Expand Up @@ -62,7 +44,7 @@
fileInput.click();
}

let isMobile = getIsMobile();
let profilePosition = $derived(getProfilePosition(data));
</script>

<div
Expand All @@ -73,82 +55,6 @@
: '@5xl/wrapper:max-w-4xl @5xl/wrapper:px-12'
]}
>
<div
class={[
'absolute left-2 z-20 flex gap-2',
profilePosition === 'side' ? 'top-2 left-14' : 'top-2'
]}
>
<Button
size="icon"
onclick={() => {
data.publication.preferences ??= {};
data.publication.preferences.hideProfileSection = true;
data = { ...data };
}}
variant="ghost"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
</Button>

<!-- Position toggle button (desktop only) -->
{#if !isMobile()}
<Button size="icon" type="button" onclick={toggleProfilePosition} variant="ghost">
{#if profilePosition === 'side'}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m19.5 4.5-15 15m0 0h11.25m-11.25 0V8.25"
/>
</svg>
{/if}
</Button>
{/if}

<!-- Theme selection -->
<SelectThemePopover
{accentColor}
{baseColor}
onchanged={(newAccent, newBase) => updateTheme(newAccent, newBase)}
/>
</div>

<div
class={[
'flex flex-col gap-4 pt-16 pb-4',
Expand Down
Loading