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

@@ -6,12 +6,18 @@
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import { theme } from "$lib/stores/theme_store";
import { getFileURL, isVideoURL, readAsDataURLAsync } from "$lib/util/file_util";
import {
getFileURL,
isVideoURL,
readAsDataURLAsync,
} from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatHTMLAsText,
formatTimeHHMM,
} from "$lib/util/format_util";
import { handleFromRecordWithIRI } from "$lib/util/activitypub_util";
let thumbnail: string = $state(
$theme === "light" ? emptyStateTrailLight : emptyStateTrailDark,
@@ -91,6 +97,18 @@
<Dropdown items={dropdownItems} {onchange}></Dropdown>
{/if}
</div>
{#if log.expand?.author}
<p class="text-xs text-gray-500 mb-3">
{$_("by")}
<img
class="rounded-full w-5 aspect-square mx-1 inline"
src={log.expand.author.icon ||
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
{handleFromRecordWithIRI(log)}
</p>
{/if}
{#if log.distance || log.elevation_gain || log.elevation_loss || log.duration}
<div
class="flex mt-1 gap-x-4 text-sm text-gray-500 flex-wrap mb-2"
@@ -115,7 +133,7 @@
>
</div>
{/if}
<span class="whitespace-pre-wrap">{log.text}</span>
<span class="whitespace-pre-wrap">{formatHTMLAsText(log.text)}</span>
</div>
</div>
</div>

View File

@@ -15,6 +15,7 @@
import PhotoPicker from "../trail/photo_picker.svelte";
import TrailPicker from "../trail/trail_picker.svelte";
import type { SummitLog } from "$lib/models/summit_log";
import Editor from "../base/editor.svelte";
interface Props {
children?: Snippet<[any]>;
onsave?: (summitLog: SummitLog) => void;
@@ -108,6 +109,7 @@
<Modal
id="summit-log-modal"
title={$data.id ? $_("edit-entry") : $_("add-entry")}
size="md:min-w-2xl"
bind:this={modal}
>
{#snippet children({ openModal })}
@@ -134,7 +136,7 @@
showThumbnailControls={false}
></PhotoPicker>
</div>
<div class="flex gap-4">
<div class="flex gap-4 items-end">
{#if $data.expand}
<TrailPicker
bind:trailFile={$data._gpx}
@@ -144,12 +146,13 @@
></TrailPicker>
{/if}
<div class="basis-full">
<Textarea
name="text"
extraClasses="h-28"
<Editor
bind:value={$data.text}
extraClasses="min-h-28"
label={$_("text")}
error={$errors.text}
></Textarea>
searchListPosition="fixed"
></Editor>
</div>
</div>
</form>

View File

@@ -10,23 +10,32 @@
import { _ } from "svelte-i18n";
import MapWithElevationMaplibre from "../trail/map_with_elevation_maplibre.svelte";
import { fetchGPX } from "$lib/stores/trail_store";
import { currentUser } from "$lib/stores/user_store";
interface Props {
summitLogs?: SummitLog[];
handle: string;
showCategory?: boolean;
showTrail?: boolean;
showAuthor?: boolean;
showRoute?: boolean;
showPhotos?: boolean;
showMenu?: boolean;
ondelete?: (summitLog: SummitLog) => void;
onedit?: (summitLog: SummitLog) => void;
}
let {
summitLogs = [],
handle,
showCategory = false,
showTrail = false,
showAuthor = false,
showRoute = false,
showPhotos = false,
showMenu = false,
ondelete,
onedit,
}: Props = $props();
let mapModal: Modal;
@@ -64,7 +73,7 @@
}
</script>
<table class="w-full">
<table class="w-full table-auto">
<thead class="text-left text-gray-500">
<tr class="text-sm">
{#if showPhotos && summitLogs.some((l) => l.photos.length)}
@@ -98,12 +107,16 @@
{$_("map")}
</th>
{/if}
{#if showMenu}
<th> </th>
{/if}
</tr>
</thead>
<tbody>
{#each summitLogs as log, i}
<SummitLogTableRow
{log}
{handle}
onopen={(log) => openMap(log)}
ontext={(log) => openText(log)}
{showCategory}
@@ -113,6 +126,9 @@
summitLogs.some((l) => l.photos.length)}
showDescription={summitLogs.some((l) => l.text?.length)}
showRoute={showRoute && summitLogs.some((l) => l.gpx)}
showMenu={showMenu && log.author == $currentUser?.actor}
{ondelete}
{onedit}
></SummitLogTableRow>
{/each}
</tbody>
@@ -122,7 +138,7 @@
{/if}
<Modal
id="summit-log-table-map-modal"
size="max-w-4xl"
size="md:min-w-2xl lg:min-w-4xl"
title=""
bind:this={mapModal}
>
@@ -137,17 +153,17 @@
</Modal>
<Modal
id="summit-log-table-text-modal"
size="max-w-xl"
size="md:min-w-xl"
title={$_("description")}
bind:this={textModal}
>
{#snippet content()}
<p class="whitespace-pre-wrap">{currentText}</p>
<div class="prose dark:prose-invert">{@html currentText}</div>
{/snippet}
</Modal>
<style>
th {
padding: 0rem 0.5rem;
padding: 0.5rem 0.5rem;
}
</style>

View File

@@ -5,38 +5,59 @@
import {
formatDistance,
formatElevation,
formatHTMLAsText,
formatTimeHHMM,
} from "$lib/util/format_util";
import { _ } from "svelte-i18n";
import PhotoGallery from "../photo_gallery.svelte";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
interface Props {
log: SummitLog;
handle: string;
showCategory?: boolean;
showTrail?: boolean;
showRoute?: boolean;
showAuthor?: boolean;
showDescription?: boolean;
showPhotos?: boolean;
showMenu?: boolean;
ontext?: (summitLog: SummitLog) => void;
onopen?: (summitLog: SummitLog) => void;
ondelete?: (summitLog: SummitLog) => void;
onedit?: (summitLog: SummitLog) => void;
}
let {
log,
handle,
showCategory = false,
showTrail = false,
showRoute = false,
showAuthor = false,
showDescription = false,
showPhotos = false,
showMenu = false,
onopen,
ontext,
ondelete,
onedit,
}: Props = $props();
let gallery: PhotoGallery;
let imgSrc: string[] = $state([]);
let dropdownItems: DropdownItem[] = [
{
text: $_("edit"),
value: "edit",
},
{
text: $_("delete"),
value: "delete",
},
];
$effect(() => {
if (log.photos?.length) {
imgSrc = log.photos
@@ -65,6 +86,14 @@
showDescription,
].reduce((b, v) => (v ? b + 1 : b), 7);
}
function handleDropdownClick(item: DropdownItem): void {
if (item.value == "edit") {
onedit?.(log);
} else if (item.value == "delete") {
ondelete?.(log);
}
}
</script>
<tr class="text-sm">
@@ -130,10 +159,7 @@
</td>
{#if showCategory}
<td>
{$_(
log.expand?.trails_via_summit_logs?.at(0)?.expand?.category
?.name ?? "-",
)}
{$_(log.expand?.trail?.expand?.category?.name ?? "-")}
</td>
{/if}
{#if showTrail}
@@ -141,8 +167,7 @@
<a
aria-label="Go to trail"
class="btn-icon aspect-square"
href="/trail/view/{log.expand?.trails_via_summit_logs?.at(0)
?.id ?? ''}"
href="/trail/view/{handle}/{log.expand?.trail?.id ?? ''}"
><i class="fa fa-arrow-up-right-from-square px-[3px]"></i></a
>
</td>
@@ -154,7 +179,7 @@
><p
class="rounded-full bg-menu-item-background-hover hover:bg-menu-item-background-focus text-ellipsis max-w-28 whitespace-nowrap overflow-hidden px-3 py-1"
>
{log.text}
{formatHTMLAsText(log.text)}
</p></button
>
{/if}
@@ -164,43 +189,54 @@
<td>
<p
class="tooltip flex justify-center"
data-title={log.expand.author.username}
data-title="{log.expand.author.username}{log.expand.author
.isLocal
? ''
: '@' + log.expand.author.domain}"
>
{#if !log.expand.author.private}
<a href="/profile/{log.expand.author.id}">
<img
class="rounded-full w-7 aspect-square"
src={getFileURL(
log.expand?.author,
log.expand?.author.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand?.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
</a>
{:else}
<a
href="/profile/@{log.expand.author.username?.toLowerCase()}{log
.expand.author.isLocal
? ''
: '@' + log.expand.author.domain}"
>
<img
class="rounded-full w-7 aspect-square"
src={getFileURL(
log.expand?.author,
log.expand?.author.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand?.author.username}&backgroundType=gradientLinear`}
src={log.expand.author.icon ||
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
{/if}
</a>
</p>
</td>
{/if}
{#if showRoute && log.gpx}
{#if showRoute}
<td>
<button
aria-label="Open route"
onclick={openRoute}
class="btn-icon"
>
<i class="fa fa-map-location-dot px-[3px] text-xl"></i></button
>
{#if log.gpx}
<button
aria-label="Open route"
onclick={openRoute}
class="btn-icon"
>
<i class="fa fa-map-location-dot px-[3px] text-xl"
></i></button
>
{/if}
</td>
{/if}
{#if showMenu}
<td>
<Dropdown onchange={handleDropdownClick} items={dropdownItems}>
{#snippet children({ toggleMenu: openDropdown })}
<button
aria-label="Open dropdown"
class="rounded-full bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-6 w-6"
onclick={openDropdown}
>
<i class="fa fa-ellipsis-vertical"></i>
</button>
{/snippet}
</Dropdown>
</td>
{/if}
</tr>