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
12 changes: 11 additions & 1 deletion web/src/lib/components/Timeline.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { MouseButton } from '$lib/DomHelper';
import { scrollY } from 'svelte/reactivity/window';
import { isVisibleNotification } from '$lib/preferences/NotificationVisibility.svelte';
import { onTimelineScrollToTop } from '$lib/timelines/ScrollToTop';

interface Props {
timeline: NewTimeline;
Expand All @@ -29,6 +30,7 @@
createdAtFormat?: 'auto' | 'time';
full?: boolean;
canTransition?: boolean;
scrollToTopTarget?: string;
}

let {
Expand All @@ -37,7 +39,8 @@
showLoading = true,
createdAtFormat = 'auto',
full = false,
canTransition = true
canTransition = true,
scrollToTopTarget
}: Props = $props();

let isTop = $state(true);
Expand Down Expand Up @@ -171,6 +174,11 @@
};

onMount(() => {
const disposeTimelineScrollToTop =
scrollToTopTarget === undefined
? undefined
: onTimelineScrollToTop(scrollToTopTarget, scrollToTop);

// Workaround for scroll position
if (!timeline.latest) {
setTimeout(() => {
Expand All @@ -180,6 +188,8 @@
newer();
}, 1000);
}

return disposeTimelineScrollToTop;
});

$effect(() => {
Expand Down
35 changes: 35 additions & 0 deletions web/src/lib/timelines/ScrollToTop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const timelineScrollToTopEvent = 'nostter:timeline-scroll-to-top';

type TimelineScrollToTopDetail = {
target: string;
};

export function requestTimelineScrollToTop(target: string): void {
if (typeof window === 'undefined') {
return;
}
window.dispatchEvent(
new CustomEvent<TimelineScrollToTopDetail>(timelineScrollToTopEvent, {
detail: { target }
})
);
}

export function onTimelineScrollToTop(
target: string,
handler: () => void | Promise<void>
): () => void {
if (typeof window === 'undefined') {
return () => {};
}
const listener = (event: Event) => {
const detail = event instanceof CustomEvent ? event.detail : undefined;
if ((detail as TimelineScrollToTopDetail | undefined)?.target !== target) {
return;
}

void handler();
};
window.addEventListener(timelineScrollToTopEvent, listener);
return () => window.removeEventListener(timelineScrollToTopEvent, listener);
}
50 changes: 41 additions & 9 deletions web/src/routes/(app)/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
import { nip19 } from 'nostr-tools';
import { _ } from 'svelte-i18n';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { followees, pubkey, rom } from '$lib/stores/Author';
import { openNoteDialog } from '$lib/stores/NoteDialog';
import { lastReadAt, notifiedEventItems } from '$lib/author/Notifications';
import NostterLogo from '$lib/components/logo/NostterLogo.svelte';
import NostterLogoIcon from '$lib/components/logo/NostterLogoIcon.svelte';
import { createDropdownMenu, melt } from '@melt-ui/svelte';
import { isVisibleNotification } from '$lib/preferences/NotificationVisibility.svelte';
import { MouseButton } from '$lib/DomHelper';
import { requestTimelineScrollToTop } from '$lib/timelines/ScrollToTop';

const {
elements: { menu, item, trigger, overlay }
Expand All @@ -31,6 +34,39 @@
$openNoteDialog = !$openNoteDialog;
}

function requestTimelineScrollToTopForCurrentLink(event: MouseEvent, link: string): boolean {
if (
event.button !== MouseButton.Left ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey ||
page.url.pathname !== link
) {
return false;
}

event.preventDefault();
requestTimelineScrollToTop(link);
return true;
}

function onClickHomeLink(event: MouseEvent): void {
requestTimelineScrollToTopForCurrentLink(event, homeLink);
}

function onClickPublicLink(event: MouseEvent): void {
requestTimelineScrollToTopForCurrentLink(event, '/public');
}

async function onClickPublicMenuItem(event: MouseEvent): Promise<void> {
if (requestTimelineScrollToTopForCurrentLink(event, '/public')) {
return;
}

await goto('/public');
}

let homeLink = $derived(
$followees.filter((x) => x !== $pubkey).length > 0 ? '/home' : '/public'
);
Expand All @@ -57,13 +93,13 @@
<nav>
<ul class="full">
<li class="clickable">
<a href={homeLink}>
<a href={homeLink} onclick={onClickHomeLink}>
<IconHome size={30} />
<p>{$_('layout.header.home')}</p>
</a>
</li>
<li class="clickable">
<a href="/public">
<a href="/public" onclick={onClickPublicLink}>
<IconWorld size={30} />
<p>{$_('pages.public')}</p>
</a>
Expand Down Expand Up @@ -128,14 +164,14 @@
</ul>
<ul class="fold">
<li>
<a href={homeLink} class="active">
<a href={homeLink} class="active" onclick={onClickHomeLink}>
<IconHome size={30} />
<p>{$_('layout.header.home')}</p>
</a>
</li>
{#if !$pubkey}
<li>
<a href="/public" class="active">
<a href="/public" class="active" onclick={onClickPublicLink}>
<IconWorld size={30} />
<p>{$_('pages.public')}</p>
</a>
Expand Down Expand Up @@ -171,11 +207,7 @@
<div use:melt={$menu} class="menu">
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
use:melt={$item}
onclick={async () => await goto(`/public`)}
class="item"
>
<div use:melt={$item} onclick={onClickPublicMenuItem} class="item">
<div class="icon"><IconWorld /></div>
<div>{$_('pages.public')}</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/routes/(app)/home/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
</header>

<div class="timeline">
<Timeline {timeline} />
<Timeline {timeline} scrollToTopTarget="/home" />
</div>

<style>
Expand Down
2 changes: 1 addition & 1 deletion web/src/routes/(app)/public/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
<h1>{$_('pages.public')}</h1>

{#if timeline !== undefined}
<Timeline {timeline} />
<Timeline {timeline} scrollToTopTarget="/public" />
{/if}
Loading