finishes feed
This commit is contained in:
@@ -139,11 +139,12 @@
|
||||
<svelte:window onscroll={onScroll} />
|
||||
|
||||
<section
|
||||
class="hero grid grid-cols-1 lg:grid-cols-2 md:px-8 md:gap-8"
|
||||
class="hero grid grid-cols-1 lg:grid-cols-2 md:px-8 gap-4 md:gap-8"
|
||||
style="min-height: calc(100vh - 112px)"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-center gap-8 max-w-md mx-8 sm:mx-auto mt-0 lg:-mt-24 md:mt-24 max-h-screen md:sticky top-0"
|
||||
class="flex flex-col justify-center gap-8 max-w-md mx-8 sm:mx-auto mt-0 lg:sticky"
|
||||
style="max-height: calc(100vh - 112px); top: 112px;"
|
||||
>
|
||||
<h2 class="text-5xl sm:text-6xl md:text-7xl font-bold">
|
||||
{$_("welcome_to")} <span class="-tracking-[0.075em]">wanderer</span>
|
||||
@@ -172,16 +173,8 @@
|
||||
<EmptyStateFeed></EmptyStateFeed>
|
||||
{/if}
|
||||
{#each feed.items as f}
|
||||
{#if f.expand.item}
|
||||
<a
|
||||
class="block"
|
||||
href={(f.type === "trail"
|
||||
? "/trail/view/"
|
||||
: "/lists/") +
|
||||
`@${f.expand.author?.preferred_username}@${f.expand.author?.domain}/${f.item}`}
|
||||
>
|
||||
<FeedCard feedItem={f}></FeedCard>
|
||||
</a>
|
||||
{#if f.expand?.item}
|
||||
<FeedCard feedItem={f}></FeedCard>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
52
web/src/routes/api/v1/profile/[handle]/feed/+server.ts
Normal file
52
web/src/routes/api/v1/profile/[handle]/feed/+server.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RecordListOptionsSchema } from '$lib/models/api/base_schema';
|
||||
import { type FeedItem } from '$lib/models/feed';
|
||||
import type { Trail } from '$lib/models/trail';
|
||||
import { Collection, handleError } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { ClientResponseError, type ListResult } from 'pocketbase';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const handle = event.params.handle;
|
||||
if (!handle) {
|
||||
return error(400, { message: "Bad request" })
|
||||
}
|
||||
|
||||
try {
|
||||
const { actor, error } = await event.locals.pb.send(`/activitypub/actor?resource=acct:${handle}`, { method: "GET", fetch: event.fetch, });
|
||||
|
||||
const searchParams = Object.fromEntries(event.url.searchParams);
|
||||
const safeSearchParams = RecordListOptionsSchema.parse(searchParams);
|
||||
|
||||
let feed: ListResult<FeedItem>;
|
||||
if (actor.isLocal) {
|
||||
feed = await event.locals.pb.collection(Collection.profile_feed)
|
||||
.getList<FeedItem>(safeSearchParams.page, safeSearchParams.perPage, { ...safeSearchParams, filter: `actor='${actor.id}'` })
|
||||
} else {
|
||||
const origin = new URL(actor.iri).origin
|
||||
const feedURL = `${origin}/api/v1/profile/${actor.preferred_username}/feed?` + event.url.searchParams
|
||||
|
||||
const response = await event.fetch(feedURL, { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
const errorResponse = await response.json()
|
||||
throw new ClientResponseError({ status: response.status, response: errorResponse });
|
||||
}
|
||||
feed = await response.json()
|
||||
|
||||
feed.items.forEach(f => {
|
||||
if (f.type == "trail") {
|
||||
const trail = f.expand.item as Trail
|
||||
trail.photos = trail.photos.map(p =>
|
||||
`${origin}/api/v1/files/trails/${f.item}/${p}`
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return json(feed)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { Actor } from '$lib/models/activitypub/actor';
|
||||
import { RecordListOptionsSchema } from '$lib/models/api/base_schema';
|
||||
import { type TimelineItem } from '$lib/models/timeline';
|
||||
import { Collection, handleError } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { ClientResponseError, type ListResult } from 'pocketbase';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const handle = event.params.handle;
|
||||
if (!handle) {
|
||||
return error(400, { message: "Bad request" })
|
||||
}
|
||||
|
||||
try {
|
||||
const {actor, error} = await event.locals.pb.send(`/activitypub/actor?resource=acct:${handle}`, { method: "GET", fetch: event.fetch, });
|
||||
|
||||
const searchParams = Object.fromEntries(event.url.searchParams);
|
||||
const safeSearchParams = RecordListOptionsSchema.parse(searchParams);
|
||||
|
||||
let timeline: ListResult<TimelineItem>;
|
||||
if (actor.isLocal) {
|
||||
timeline = await event.locals.pb.collection(Collection.timeline)
|
||||
.getList<TimelineItem>(safeSearchParams.page, safeSearchParams.perPage, { ...safeSearchParams, filter: `author='${actor.iri}'` })
|
||||
} else {
|
||||
const origin = new URL(actor.iri).origin
|
||||
const timelineURL = `${origin}/api/v1/profile/${actor.preferred_username}/timeline?` + event.url.searchParams
|
||||
|
||||
const response = await event.fetch(timelineURL, { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
const errorResponse = await response.json()
|
||||
throw new ClientResponseError({ status: response.status, response: errorResponse });
|
||||
}
|
||||
timeline = await response.json()
|
||||
|
||||
timeline.items.forEach(i => {
|
||||
i.photos = i. photos.map(p =>
|
||||
`${origin}/api/v1/files/${i.type}s/${i.id}/${p}`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return json(timeline)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,28 @@
|
||||
import { page } from "$app/state";
|
||||
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
|
||||
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
|
||||
import ActivityCard from "$lib/components/profile/activity_card.svelte";
|
||||
import FeedCard from "$lib/components/profile/feed_card.svelte";
|
||||
import type { FeedItem } from "$lib/models/feed.js";
|
||||
|
||||
import type { TimelineItem } from "$lib/models/timeline.js";
|
||||
import { profile_timeline_index } from "$lib/stores/profile_store.js";
|
||||
import { profile_feed_index } from "$lib/stores/profile_store.js";
|
||||
import { theme } from "$lib/stores/theme_store.js";
|
||||
import { getFileURL } from "$lib/util/file_util.js";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let timeline = $state(data.timeline);
|
||||
$effect(() => {
|
||||
data.feed;
|
||||
feed = data.feed
|
||||
})
|
||||
|
||||
let feed = $state(data.feed);
|
||||
|
||||
let loading: boolean = false;
|
||||
|
||||
let pagination = $derived({
|
||||
page: timeline.page,
|
||||
totalPages: timeline.totalItems,
|
||||
page: feed.page,
|
||||
totalPages: feed.totalItems,
|
||||
});
|
||||
|
||||
async function onListScroll(e: Event) {
|
||||
@@ -37,19 +42,19 @@
|
||||
|
||||
async function loadNextPage() {
|
||||
pagination.page += 1;
|
||||
timeline = await profile_timeline_index(
|
||||
page.params.handle,
|
||||
pagination.page,
|
||||
);
|
||||
feed = await profile_feed_index(page.params.handle, pagination.page);
|
||||
}
|
||||
|
||||
function handleTimeLineItemClick(item: TimelineItem) {
|
||||
if (item.trail_iri.length) {
|
||||
function handleFeedItemClick(f: FeedItem) {
|
||||
const author = f.expand.item.expand?.author;
|
||||
if (f.type == "trail") {
|
||||
goto(
|
||||
`/trail/view/@${item.trail_author_username}@${item.trail_author_domain}/${item.trail_id}`,
|
||||
`/trail/view/@${author?.preferred_username}@${author?.domain}/${f.item}`,
|
||||
);
|
||||
} else {
|
||||
goto(`/trail/view/${page.params.handle}/${item.trail_id}`);
|
||||
goto(
|
||||
`/lists/@${author?.preferred_username}@${author?.domain}/${f.item}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -130,24 +135,24 @@
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-xl font-semibold">Timeline</h4>
|
||||
{#if !timeline.items?.length && data.isOwnProfile}
|
||||
{#if !feed.items?.length && data.isOwnProfile}
|
||||
<a class="btn-primary inline-block" href="/trails/edit/new"
|
||||
>+ {$_("new-trail")}</a
|
||||
>
|
||||
{:else if !timeline.items?.length}
|
||||
{:else if !feed.items?.length}
|
||||
<p class="w-full text-center text-gray-500 text-sm">
|
||||
{$_("empty-activities", {
|
||||
values: { username: data.profile.preferredUsername },
|
||||
})}
|
||||
</p>
|
||||
{/if}
|
||||
{#each timeline.items as item}
|
||||
{#each feed.items as item}
|
||||
<div
|
||||
class="py-1 cursor-pointer"
|
||||
role="presentation"
|
||||
onclick={() => handleTimeLineItemClick(item)}
|
||||
onclick={() => handleFeedItemClick(item)}
|
||||
>
|
||||
<ActivityCard activity={item} actor={data.actor}></ActivityCard>
|
||||
<FeedCard feedItem={item}></FeedCard>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ListFilter } from "$lib/models/list";
|
||||
import { profile_lists_index, profile_timeline_index } from "$lib/stores/profile_store";
|
||||
import { profile_lists_index, profile_feed_index } from "$lib/stores/profile_store";
|
||||
import { error, type Load } from "@sveltejs/kit";
|
||||
|
||||
export const load: Load = async ({ params, fetch, parent }) => {
|
||||
@@ -19,10 +19,10 @@ export const load: Load = async ({ params, fetch, parent }) => {
|
||||
|
||||
try {
|
||||
const lists = await profile_lists_index(params.handle, filter, 1, 6, fetch)
|
||||
const timeline = await profile_timeline_index(params.handle, 1, 10, fetch);
|
||||
return { lists: lists.items, timeline }
|
||||
const feed = await profile_feed_index(params.handle, 1, 10, fetch);
|
||||
return { lists: lists.items, feed }
|
||||
} catch(e) {
|
||||
return {lists: [], timeline: {items: [], page: 1, perPage: 1, totalItems: 0, totalPages: 0}}
|
||||
return {lists: [], feed: {items: [], page: 1, perPage: 1, totalItems: 0, totalPages: 0}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user