Federation (#327)

* initial commit federation

* more federation

* more federation

* more federation

* completes follow, accept, undo

* add trail create activity

* process trail create activity

* more trail create activity

* adds missing endpoints

* adds update and delete activity

* adds comment activities

* adds activities back

* adds summit logs activities

* deletes follow counts table

* fixes migrations

* fixes migrations

* fixes migrations

* fixes migrations

* adds remote profiles

* ctd

* ctd

* we are getting closer...

* adds public summit logs

* fixes federated trails in lists

* adds remote lists

* adds list activites

* fixes lists

* adds notifications

* adds iri redirect

* fixes comments and summitlogs

* fixes follows and profiles

* adds asynchronous send

* fixes list search

* adds encryption key

* bug fixes

* fixes activity signing

* removes custon activity object types

* adds html editor

* fixes html editor

* fixes display issues on mastodon

* adds federated sharing

* finishes announcements

* fixes small summit log issues

* adds trail likes

* finalizes likes

* fixes images for komoot

* add disable federation option

* adds private profiles

* updates docs

* adds federated comments

* adds trail and comments actvitiypub routes

* adds mentions to editor

* adds mentions to trails, comments, summit logs

* updates theme

* updates theme

* update docs

* update docs

* updates docs

* fixes various frontend problems

* fixes activitypub follows api

* updates docs

* updates docs

---------

Co-authored-by: Christian Beutel <>
This commit is contained in:
Flomp
2025-06-21 10:09:22 +02:00
committed by GitHub
parent 742a6d9d30
commit 9276690d60
356 changed files with 57150 additions and 35470 deletions

View File

@@ -0,0 +1,159 @@
<script lang="ts">
import { goto } from "$app/navigation";
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 type { TimelineItem } from "$lib/models/timeline.js";
import { profile_timeline_index } from "$lib/stores/profile_store.js";
import { theme } from "$lib/stores/theme_store.js";
import { getFileURL } from "$lib/util/file_util.js";
import { HTML } from "@threlte/extras";
import { _ } from "svelte-i18n";
import { temp } from "three/examples/jsm/nodes/Nodes.js";
let { data } = $props();
let timeline = $derived(data.timeline);
let loading: boolean = false;
let pagination = $derived({
page: 1,
totalPages: data.timeline.totalItems,
});
async function onListScroll(e: Event) {
if (
window.innerHeight + window.scrollY >=
0.8 * document.body.offsetHeight &&
pagination.page !== pagination.totalPages &&
!loading
) {
loading = true;
await loadNextPage();
loading = false;
}
}
async function loadNextPage() {
pagination.page += 1;
data.timeline = await profile_timeline_index(
data.actor.iri,
pagination.page,
);
}
function handleTimeLineItemClick(item: TimelineItem) {
if (item.trail_iri.length) {
const originalTrailId = item.trail_iri.substring(
item.trail_iri.length - 15,
);
goto(
`/trail/view/@${item.trail_author_username}@${item.trail_author_domain}/${item.trail_id}`,
);
} else {
goto(`/trail/view/${page.params.handle}/${item.trail_id}`);
}
}
</script>
<svelte:window onscroll={onListScroll} />
<svelte:head>
<title>{$_("profile")} | wanderer</title>
</svelte:head>
<div class="space-y-6">
<div class="space-y-4">
<h4 class="text-xl font-semibold">
{$_("about")}
{data.profile.preferredUsername}
{#if data.isOwnProfile && data.profile.bio.length}
<a aria-label="Edit bio" class="ml-4" href="/settings/profile"
><i class="fa fa-pen text-base"></i></a
>
{/if}
</h4>
{#if data.profile.bio.length}
<p class="prose dark:prose-invert">{@html data.profile.bio}</p>
{:else if data.isOwnProfile}
<a class="btn-primary inline-block" href="/settings/profile"
>+ {$_("add-bio")}</a
>
{:else}
<p class="w-full text-center text-gray-500 text-sm">
{$_("empty-bio", {
values: { username: data.profile.preferredUsername },
})}
</p>
{/if}
</div>
<div class="space-y-4">
<h4 class="text-xl font-semibold">
{$_("list", { values: { n: 2 } })}
</h4>
<div class="flex gap-x-4 overflow-x-scroll">
{#if !data.lists.length && data.isOwnProfile}
<a class="btn-primary inline-block" href="/lists/edit/new"
>+ {$_("new-list")}</a
>
{:else if !data.lists.length}
<p class="w-full text-center text-gray-500 text-sm">
{$_("empty-lists", {
values: { username: data.profile.preferredUsername },
})}
</p>
{/if}
{#each data.lists as list}
<a
href="/lists/{page.params.handle}/{list.id}"
class="relative w-64 h-48 rounded-xl overflow-hidden group shrink-0"
>
<img
class="w-full h-full object-cover transition-transform group-hover:scale-110"
src={list.avatar
? getFileURL(list, list.avatar)
: $theme === "light"
? emptyStateTrailLight
: emptyStateTrailDark}
alt="avatar"
/>
<div
class="absolute bottom-0 w-full h-2/3 bg-gradient-to-b from-transparent to-black opacity-50"
></div>
<h5
class="absolute bottom-4 left-4 font-semibold text-white"
>
{list.name}
</h5>
</a>
{/each}
</div>
</div>
<div class="space-y-4">
<h4 class="text-xl font-semibold">Timeline</h4>
{#if !timeline.items?.length && data.isOwnProfile}
<a class="btn-primary inline-block" href="/trails/edit/new"
>+ {$_("new-trail")}</a
>
{:else if !timeline.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}
<div
class="py-1 cursor-pointer"
role="presentation"
onclick={() => handleTimeLineItemClick(item)}
>
<ActivityCard activity={item} actor={data.actor}></ActivityCard>
</div>
{/each}
</div>
</div>