Skip to content

Commit

Permalink
fix: improve UX for adding links to profile.
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Dec 19, 2024
1 parent c0dcc6a commit 1168ca1
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 82 deletions.
72 changes: 44 additions & 28 deletions src/lib/components/editors/LinksEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
import { Handle } from '@rodrigodagostino/svelte-sortable-list';
import { IconHandle } from '@rodrigodagostino/svelte-sortable-list';
import SocialMediaButton from '../social-media/social-media-button.svelte';
import { getFeaturedSocialMediaDetails } from '$lib/utils/social-links';
import FeaturedSocialMediaButton from '../social-media/featured-social-media-button.svelte';
let {
links = $bindable(),
...attrs
}: { links: { label?: string; url: string }[] } & HTMLAttributes<HTMLDivElement> = $props();
let newLabel = $state('');
let newUrl = $state('');
let newLink = $state({ label: '', url: '' });
let ids: Map<object, string> = $state(new Map());
Expand All @@ -29,40 +30,51 @@
return id;
}
let error = $state('');
function handleSort(event: CustomEvent<SortEventDetail>) {
const { prevItemIndex: from, nextItemIndex: to } = event.detail;
let clone = [...links];
clone.splice(to < 0 ? clone.length + to : to, 0, clone.splice(from, 1)[0]);
links = clone;
}
async function fetchURL() {
error = '';
try {
const url = new URL(newUrl);
if (url) {
const resp = await fetch(`/api/links?url=${newUrl}`);
const fetchURL = async () => {
const url = new URL(newLink.url);
if (url) {
const resp = await fetch(`/api/links?url=${newLink.url}`);
if (resp.status == 200) {
const htmlData = await resp.text();
const parser = new DOMParser();
const doc = parser.parseFromString(htmlData, 'text/html');
const title = doc.querySelector('title')?.innerText;
if (!title) {
error = 'Title not found';
if (!title || title.startsWith('ERROR')) {
newLink.label = '';
}
newLabel = title ?? '';
newLink.label = title ?? '';
}
} catch (err: any) {
error = 'Invalid link';
}
}
};
$effect(() => {
if (newUrl) {
if (newLink.url && !newLink.label) {
fetchURL();
}
});
$effect(() => {
if (newLink.url.length > 0 && !links.includes(newLink)) {
links = [...links, newLink];
} else if (newLink.url.length == 0 && links.includes(newLink)) {
links = links.filter((x) => x !== newLink);
}
});
function host(url: string): string | undefined {
try {
return new URL(url).host;
} catch (_) {}
}
let featuredLinks = $derived(links.filter((x) => getFeaturedSocialMediaDetails(x.url)));
</script>

<div class="flex flex-col" {...attrs}>
Expand All @@ -78,17 +90,12 @@
class="mb-4 flex items-center gap-2"
onsubmit={(e) => {
e.preventDefault();
links = [...links, { label: newLabel, url: newUrl }];
newLabel = '';
newUrl = '';
newLink = { label: '', url: '' };
}}
>
<div class="flex flex-grow flex-col items-center justify-center">
{#if newLabel}
<input class="input mb-2" placeholder="Label" bind:value={newLabel} />
{/if}
<input required class="input" placeholder="Url" bind:value={newUrl} />
<div class="text-sm text-error-400">{error}</div>
<div class="flex flex-grow flex-col items-center justify-center gap-2">
<input class="input" placeholder="Label" bind:value={newLink.label} />
<input required class="input" placeholder="Url" bind:value={newLink.url} />
</div>

<div class="flex items-center">
Expand All @@ -104,12 +111,21 @@
<Handle>
<IconHandle />
</Handle>
<SocialMediaButton isEditing url={link.url} />
{#if featuredLinks.includes(link)}
<FeaturedSocialMediaButton url={link.url} />
{:else}
<SocialMediaButton url={link.url} label={link.label || host(link.url)} />
{/if}

<button
class="variant-ghost btn-icon btn-icon-sm"
title="Delete link"
onclick={() => links.splice(index, 1)}>x</button
onclick={() => {
const l = links.splice(index, 1)[0];
if (l == newLink) {
newLink = { label: '', url: '' };
}
}}>x</button
>
</li>
</SortableItem>
Expand Down
25 changes: 12 additions & 13 deletions src/lib/components/social-media/featured-social-media-button.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
<script>
import { getFeaturedSocialMediaDetails } from '$lib/utils/social-links';
import { getFeaturedSocialMediaDetails, getSocialMediaDetails } from '$lib/utils/social-links';
import Icon from '@iconify/svelte';
export let url;
const socialMedia = getSocialMediaDetails(url);
const featuredSocialMedia = getFeaturedSocialMediaDetails(url);
</script>

{#if featuredSocialMedia}
<a
href={url}
target="_blank"
title={featuredSocialMedia.name}
class="variant-outline-primary btn btn-icon-sm"
>
<span>
<Icon icon={featuredSocialMedia.icon} class="h-6 w-6" />
</span>
</a>
{/if}
<a
href={url}
target="_blank"
title={featuredSocialMedia?.name || socialMedia.name}
class="variant-outline-primary btn btn-icon-sm"
>
<span>
<Icon icon={featuredSocialMedia?.icon || socialMedia.icon} class="h-6 w-6" />
</span>
</a>
23 changes: 10 additions & 13 deletions src/lib/components/social-media/social-media-button.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
<script>
import { getFeaturedSocialMediaDetails, getSocialMediaDetails } from '$lib/utils/social-links';
import { getSocialMediaDetails } from '$lib/utils/social-links';
import Icon from '@iconify/svelte';
export let label = '';
export let url;
export let isEditing = false;
const socialMedia = getSocialMediaDetails(url);
const featuredSocialMedia = getFeaturedSocialMediaDetails(url);
</script>

{#if isEditing || !featuredSocialMedia}
<a href={url} target="_blank" class="variant-filled btn">
{#if socialMedia?.icon}
<span>
<Icon icon={socialMedia.icon} class="h-6 w-6" />
</span>
{/if}
<a href={url} target="_blank" class="variant-filled btn">
{#if socialMedia?.icon}
<span>
<Icon icon={socialMedia.icon} class="h-6 w-6" />
</span>
{/if}

<span>{socialMedia?.name} </span>
</a>
{/if}
<span>{label || socialMedia?.name} </span>
</a>
8 changes: 4 additions & 4 deletions src/lib/utils/social-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export const socialMediaConfig: Record<string, SocialMediaConfigEntry> = {
'soundcloud.com': { icon: 'mdi:soundcloud', class: 'button-soundcloud', name: 'SoundCloud' }
};

export function getDomain(url: string) {
export function getDomain(url: string): string {
try {
return new URL(url).hostname.replace('www.', '');
} catch (error) {
return null; // Return null if the URL is invalid
return 'invalid.url'; // Return null if the URL is invalid
}
}

export function getSocialMediaDetails(url: string) {
export function getSocialMediaDetails(url: string): SocialMediaConfigEntry {
const domain = getDomain(url);
const socialMedia = domain ? socialMediaConfig[domain] : null;

Expand All @@ -67,7 +67,7 @@ export function getSocialMediaDetails(url: string) {
);
}

export function getFeaturedSocialMediaDetails(url: string) {
export function getFeaturedSocialMediaDetails(url: string): SocialMediaConfigEntry | null {
const domain = getDomain(url);
const socialMedia = domain ? featuredSocialMediaConfig[domain] : null;
return socialMedia;
Expand Down
10 changes: 9 additions & 1 deletion src/routes/(app)/[username]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { setProfileById, setAvatarById, type Profile } from '$lib/leaf/profile';
import { type Actions, redirect, fail, error } from '@sveltejs/kit';
import { type Actions, redirect, fail } from '@sveltejs/kit';
import { type CheckResponseError } from '$lib/utils/http';
import { getSession } from '$lib/rauthy/server';
import { RawImage } from 'leaf-proto/components';
Expand Down Expand Up @@ -29,6 +29,14 @@ export const actions = {
let links: { label?: string; url: string }[] = JSON.parse(
data.get('links')?.toString() || '{}'
);
for (const link of links) {
try {
new URL(link.url);
} catch (_) {
// If it isn't a valid URL, try just prepending `https://` to it.
link.url = 'https://' + link.url;
}
}
let bio = data.get('bio')?.toString() || undefined;
if (bio === '') {
bio = undefined;
Expand Down
26 changes: 12 additions & 14 deletions src/routes/(app)/[username]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@
import SocialMediaButton from '$lib/components/social-media/social-media-button.svelte';
import FeaturedSocialMediaButton from '$lib/components/social-media/featured-social-media-button.svelte';
import PostCard from './post-card.svelte';
import { getFeaturedSocialMediaDetails } from '$lib/utils/social-links';
let { data, form }: { data: PageData; form: ActionData } = $props();
const modalStore = getModalStore();
const toastStore = getToastStore();
let normalProfileLinks = $derived(
data.profile.links.filter((x) => !getFeaturedSocialMediaDetails(x.url))
);
let featuredProfileLinks = $derived(
data.profile.links.filter((x) => getFeaturedSocialMediaDetails(x.url))
);
const githubImportModal: ModalSettings = {
type: 'prompt',
// Data
Expand Down Expand Up @@ -63,16 +71,6 @@
editingState.profile = data.profile;
}
// Here we have to hack a unique ID in for each of the links so that it can be used by the
// LinksEditor component.
//
// It'd be good to find a better way to do this.
$effect(() => {
for (const link of editingState.profile.links) {
if (!(link as any).id) (link as any).id = Math.random().toString();
}
});
let profile = $derived(data.profile as Profile);
let pubpageHost = $derived(data.username);
let pubpageUrl = $derived(`${new URL(env.PUBLIC_URL).protocol}//${pubpageHost}`);
Expand Down Expand Up @@ -199,7 +197,7 @@
>
{#if !editingState.editing}
<div class="mt-4 flex flex-wrap items-center gap-4">
{#each profile.links as link}
{#each featuredProfileLinks as link}
<FeaturedSocialMediaButton url={link.url} />
{/each}
</div>
Expand Down Expand Up @@ -274,13 +272,13 @@
/>
{/if}
</div>
{#if profile.links.length > 0 || editingState.editing}
{#if normalProfileLinks.length > 0 || editingState.editing}
<div>
<h2 class="mb-3 text-center text-2xl font-bold">Links</h2>
{#if !editingState.editing}
<ul class="flex flex-col items-center gap-2">
{#each profile.links as link (link.url)}
<SocialMediaButton url={link.url} />
{#each normalProfileLinks as link (link.url)}
<SocialMediaButton url={link.url} label={link.label} />
{/each}
</ul>
{:else}
Expand Down
22 changes: 13 additions & 9 deletions src/routes/api/links/+server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { error } from '@sveltejs/kit';

export async function GET(event) {
const link = event.url.searchParams.get('url');
export async function GET({ url, fetch }) {
const link = url.searchParams.get('url');
if (!link) error(400, { message: 'Link is missing' });

const url = new URL(link);

const response = await fetch(url);
const html = await response.text();

return new Response(JSON.stringify(html));
try {
const resp = await fetch(link);
console.log(resp);
if (resp.ok) {
const text = await resp.text();
return new Response(text);
}
return error(500, { message: 'Error fetching URL' });
} catch (e) {
return error(500, { message: 'Error fetching URL' });
}
}

0 comments on commit 1168ca1

Please sign in to comment.