diff --git a/src/lib/api.js b/src/lib/api.js index 07644a3..812ddd0 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -2,6 +2,31 @@ const errors = { AUTHENTICATION_FAILED: "AUTHENTICATION_FAILED", }; +/** + * Parses a HTTP Link header + * @param {string} header - the HTTP Link header string + */ +function _parseLinkHeader(header) { + // remove whitespace and split + let links = header.replace(/\ /g, "").split(","); + + return links.map(l => { + let parts = l.split(";"); + + // assuming 0th is URL, removing <> + let url = new URL(parts[0].slice(1, -1)); + + // get rel inbetween double-quotes + let rel = parts[1].match(/"(.*?)"/g)[0].slice(1, -1); + + return { + url, rel + } + }) +} + +_parseLinkHeader(`; rel="next", ; rel="prev"`) + /** * GET /api/v1/instance * @param {string} host - The domain of the target server. @@ -242,10 +267,42 @@ export async function rejectFollowRequest(host, token, account_id) { * @param {string} token - The application token. * @param {string} timeline - The name of the timeline to pull (default "home"). * @param {string} max_id - If provided, only shows posts after this ID. + * @param {boolean} local_only - If provided, only shows posts from the local instance + * @param {boolean} remote_only - If provided, only shows posts from other instances */ -export async function getTimeline(host, token, timeline, max_id) { +export async function getTimeline(host, token, timeline, max_id, local_only, remote_only) { let url = `https://${host}/api/v1/timelines/${timeline || "home"}`; + let params = new URLSearchParams(); + if (max_id) params.append("max_id", max_id); + if (remote_only) params.append("remote", remote_only); + if (local_only) params.append("local", local_only); + const params_string = params.toString(); + if (params_string) url += '?' + params_string; + + const data = await fetch(url, { + method: 'GET', + headers: { "Authorization": token ? `Bearer ${token}` : null } + }) + + let links = _parseLinkHeader(data.headers.get("Link")); + + return { + data: await data.json(), + prev: links.find(f=>f.rel=="prev"), + next: links.find(f=>f.rel=="next") + }; +} + +/** + * GET /api/v1/favourites + * @param {string} host - The domain of the target server. + * @param {string} token - The application token. + * @param {string} max_id - If provided, only shows posts after this ID. + */ +export async function getFavourites(host, token, max_id) { + let url = `https://${host}/api/v1/favourites`; + let params = new URLSearchParams(); if (max_id) params.append("max_id", max_id); const params_string = params.toString(); @@ -254,9 +311,15 @@ export async function getTimeline(host, token, timeline, max_id) { const data = await fetch(url, { method: 'GET', headers: { "Authorization": token ? `Bearer ${token}` : null } - }).then(res => res.json()); + }) - return data; + let links = _parseLinkHeader(data.headers.get("Link")); + + return { + data: await data.json(), + prev: links.find(f=>f.rel=="prev"), + next: links.find(f=>f.rel=="next") + }; } /** @@ -555,4 +618,4 @@ export async function getUserPinnedPosts(host, token, user_id) { }).then(res => res.json()); return data; -} +} \ No newline at end of file diff --git a/src/lib/timeline.js b/src/lib/timeline.js index 66db44d..68aa2b2 100644 --- a/src/lib/timeline.js +++ b/src/lib/timeline.js @@ -10,21 +10,38 @@ export const timeline = writable([]); const lang = Lang(); let loading = false; +let last_post = false; -export async function getTimeline(clean) { +export async function getTimeline(timelineType = "home", clean, localOnly = false, remoteOnly = false) { if (loading) return; // no spamming!! loading = true; - let last_post = false; - if (!clean && get(timeline).length > 0) - last_post = get(timeline)[get(timeline).length - 1].id; + if(clean) { + timeline.set([]); + last_post = false; + } - const timeline_data = await api.getTimeline( - get(server).host, - get(app).token, - "home", - last_post - ); + let timeline_data; + switch(timelineType) { + case "favourites": + timeline_data = await api.getFavourites( + get(server).host, + get(app).token, + last_post + ) + break; + + default: + timeline_data = await api.getTimeline( + get(server).host, + get(app).token, + timelineType, + last_post, + localOnly, + remoteOnly + ); + break; + } if (!timeline_data) { console.error(lang.string('logs.timeline_fetch_failed')); @@ -32,10 +49,12 @@ export async function getTimeline(clean) { return; } - if (clean) timeline.set([]); + if (!clean) { + last_post = timeline_data.next.url.searchParams.get("max_id") + } - for (let i in timeline_data) { - const post_data = timeline_data[i]; + for (let i in timeline_data.data) { + const post_data = timeline_data.data[i]; const post = await parsePost(post_data, 1); if (!post) { if (post === null || post === undefined) { diff --git a/src/lib/ui/Navigation.svelte b/src/lib/ui/Navigation.svelte index 384450d..abfacf8 100644 --- a/src/lib/ui/Navigation.svelte +++ b/src/lib/ui/Navigation.svelte @@ -14,18 +14,18 @@ import Logo from '$lib/../img/campfire-logo.svg'; import Button from './Button.svelte'; - import TimelineIcon from '../../img/icons/timeline.svg'; - import NotificationsIcon from '../../img/icons/notifications.svg'; - import ExploreIcon from '../../img/icons/explore.svg'; - import ListIcon from '../../img/icons/lists.svg'; - import FavouritesIcon from '../../img/icons/like_fill.svg'; - import BookmarkIcon from '../../img/icons/bookmark.svg'; - import HashtagIcon from '../../img/icons/hashtag.svg'; - import PostIcon from '../../img/icons/post.svg'; - import InfoIcon from '../../img/icons/info.svg'; - import SettingsIcon from '../../img/icons/settings.svg'; - import LogoutIcon from '../../img/icons/logout.svg'; - import FollowersIcon from '../../img/icons/followers.svg'; + import TimelineIcon from '@cf/icons/timeline.svg'; + import NotificationsIcon from '@cf/icons/notifications.svg'; + import ExploreIcon from '@cf/icons/explore.svg'; + import ListIcon from '@cf/icons/lists.svg'; + import FavouritesIcon from '@cf/icons/like_fill.svg'; + import BookmarkIcon from '@cf/icons/bookmark.svg'; + import HashtagIcon from '@cf/icons/hashtag.svg'; + import PostIcon from '@cf/icons/post.svg'; + import InfoIcon from '@cf/icons/info.svg'; + import SettingsIcon from '@cf/icons/settings.svg'; + import LogoutIcon from '@cf/icons/logout.svg'; + import FollowersIcon from '@cf/icons/followers.svg'; const VERSION = APP_VERSION; const lang = Lang(); @@ -124,7 +124,10 @@
- - - + + + +
diff --git a/src/routes/favourites/+page.svelte b/src/routes/favourites/+page.svelte new file mode 100644 index 0000000..0bd0331 --- /dev/null +++ b/src/routes/favourites/+page.svelte @@ -0,0 +1,36 @@ + + + + +
+ {#if $timeline.length <= 0} +
+ {lang.string('timeline.fetching')} +
+ {/if} + {#each $timeline as post} + + {/each} +
\ No newline at end of file diff --git a/src/routes/follow-requests/+page.svelte b/src/routes/follow-requests/+page.svelte index c6e62fb..f37a69a 100644 --- a/src/routes/follow-requests/+page.svelte +++ b/src/routes/follow-requests/+page.svelte @@ -7,8 +7,8 @@ import Button from '../../lib/ui/Button.svelte'; import * as api from '$lib/api' - import TickIcon from '../../img/icons/tick.svg' - import CrossIcon from '../../img/icons/cross.svg' + import TickIcon from '@cf/icons/tick.svg' + import CrossIcon from '@cf/icons/cross.svg' import { get } from 'svelte/store'; const lang = Lang();