Improve the timeline UI and spacing
This commit is contained in:
parent
7c6bded69e
commit
fa56d0252d
2 changed files with 71 additions and 27 deletions
|
|
@ -1,7 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { page } from "$app/state";
|
||||
import { threadDetailStore, loadThread, sendReply, type PostData } from "$lib/thread.svelte";
|
||||
import {
|
||||
threadDetailStore,
|
||||
loadThread,
|
||||
sendReply,
|
||||
type PostData,
|
||||
} from "$lib/thread.svelte";
|
||||
import { auth, login } from "$lib/auth.svelte";
|
||||
import Reactions from "$lib/components/Reactions.svelte";
|
||||
import ThreadScrubber from "$lib/components/ThreadScrubber.svelte";
|
||||
|
|
@ -9,7 +14,10 @@
|
|||
|
||||
type Author = { pubkey: string; name: string; picture?: string };
|
||||
|
||||
function resolveAuthor(pubkey: string, profiles: Record<string, NostrUser>): Author {
|
||||
function resolveAuthor(
|
||||
pubkey: string,
|
||||
profiles: Record<string, NostrUser>,
|
||||
): Author {
|
||||
const user = profiles[pubkey];
|
||||
return {
|
||||
pubkey,
|
||||
|
|
@ -47,13 +55,12 @@
|
|||
}
|
||||
}
|
||||
let opTopOffset = $state(0);
|
||||
let scrubberVisible = $state(false);
|
||||
|
||||
const detail = $derived(threadDetailStore.detail);
|
||||
const profiles = $derived(threadDetailStore.profiles);
|
||||
|
||||
const allPosts = $derived(
|
||||
detail ? [detail.op, ...detail.replies] : []
|
||||
);
|
||||
const allPosts = $derived(detail ? [detail.op, ...detail.replies] : []);
|
||||
const postEls = $derived([opEl, ...replyEls]);
|
||||
|
||||
// Reload when navigating between threads
|
||||
|
|
@ -65,7 +72,9 @@
|
|||
const main = document.querySelector("main");
|
||||
if (!main) return;
|
||||
main.classList.add("no-scrollbar");
|
||||
const onScroll = () => { isScrolled = main.scrollTop > 0; };
|
||||
const onScroll = () => {
|
||||
isScrolled = main.scrollTop > 0;
|
||||
};
|
||||
main.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => {
|
||||
main.classList.remove("no-scrollbar");
|
||||
|
|
@ -88,9 +97,15 @@
|
|||
|
||||
{#snippet avatar(author: Author)}
|
||||
{#if author.picture}
|
||||
<img src={author.picture} alt="" class="w-12 h-12 rounded-full object-cover" />
|
||||
<img
|
||||
src={author.picture}
|
||||
alt=""
|
||||
class="w-12 h-12 rounded-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<span class="w-12 h-12 flex items-center justify-center rounded-full bg-gray-200 text-lg font-semibold text-gray-500">
|
||||
<span
|
||||
class="w-12 h-12 flex items-center justify-center rounded-full bg-gray-200 text-lg font-semibold text-gray-500"
|
||||
>
|
||||
{author.name[0].toUpperCase()}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -105,7 +120,9 @@
|
|||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-baseline justify-between mb-2">
|
||||
<span class="font-medium text-gray-600">{author.name}</span>
|
||||
<span class="text-sm text-gray-400 ml-4 flex-shrink-0">{formatDate(p.createdAt)}</span>
|
||||
<span class="text-sm text-gray-400 ml-4 flex-shrink-0"
|
||||
>{formatDate(p.createdAt)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="prose leading-5 max-w-none text-gray-700">
|
||||
{#each p.content.split("\n\n") as para}
|
||||
|
|
@ -114,7 +131,9 @@
|
|||
</div>
|
||||
<div class="flex items-center justify-between mt-3">
|
||||
<Reactions reactions={[]} zaps={0} />
|
||||
<div class="flex items-center gap-4 text-sm text-gray-400 flex-shrink-0">
|
||||
<div
|
||||
class="flex items-center gap-4 text-sm text-gray-400 flex-shrink-0"
|
||||
>
|
||||
<button class="hover:text-brand transition-colors">Quote</button>
|
||||
<button class="hover:text-brand transition-colors">React</button>
|
||||
<button class="hover:text-amber-500 transition-colors">⚡ Zap</button>
|
||||
|
|
@ -126,21 +145,25 @@
|
|||
|
||||
{#if detail}
|
||||
<div class="flex gap-6 items-start">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sticky -top-6 bg-white z-10 pb-4 -mx-10 px-10 pt-6 -mt-6 relative">
|
||||
<div class="flex-1 min-w-0" class:pr-18={!scrubberVisible}>
|
||||
<div
|
||||
class="sticky -top-6 bg-white z-10 pb-4 -mx-10 px-10 pt-6 -mt-6 relative"
|
||||
>
|
||||
<h1 class="text-[1.65rem] text-brand leading-7">{detail.title}</h1>
|
||||
<div
|
||||
class="absolute left-0 right-0 h-8 pointer-events-none transition-opacity duration-200"
|
||||
style="top: 100%; opacity: {isScrolled ? 1 : 0}; background: linear-gradient(to bottom, white, transparent);"
|
||||
style="top: 100%; opacity: {isScrolled
|
||||
? 1
|
||||
: 0}; background: linear-gradient(to bottom, white, transparent);"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 pb-6 border-b border-gray-100" bind:this={opEl}>
|
||||
<div class="pt-6 pb-6" bind:this={opEl}>
|
||||
{@render post(detail.op, () => {})}
|
||||
</div>
|
||||
|
||||
{#if detail.replies.length > 0}
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div class="divide-y divide-gray-100 border-t border-gray-100">
|
||||
{#each detail.replies as reply, i}
|
||||
<div class="py-6" bind:this={replyEls[i]}>
|
||||
{@render post(reply, () => {})}
|
||||
|
|
@ -149,10 +172,12 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-8 border-t border-gray-200 pt-6">
|
||||
<div class="mt-8 border-t border-gray-200 pt-6 pl-18">
|
||||
{#if auth.user}
|
||||
{#if replyError}
|
||||
<div class="mb-4 rounded bg-red-50 px-4 py-3 text-sm text-red-700 border border-red-200">
|
||||
<div
|
||||
class="mb-4 rounded bg-red-50 px-4 py-3 text-sm text-red-700 border border-red-200"
|
||||
>
|
||||
{replyError}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -173,15 +198,17 @@
|
|||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm text-gray-500">
|
||||
To participate and reply,
|
||||
<button onclick={login} class="text-brand hover:underline">login now</button>
|
||||
<p class=" text-gray-500 text-center">
|
||||
To participate and reply, please
|
||||
<button onclick={login} class="text-brand hover:underline"
|
||||
>login now</button
|
||||
>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ThreadScrubber posts={allPosts} {postEls} topOffset={opTopOffset} />
|
||||
<ThreadScrubber posts={allPosts} {postEls} topOffset={opTopOffset} bind:visible={scrubberVisible} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="py-12 text-center text-gray-400">Loading…</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue