Display replies on the post page
parent
55030b1e54
commit
a8f518f4b8
|
@ -3,6 +3,6 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
declare namespace App {
|
||||
interface Session {
|
||||
instance: { url: string; token?: string };
|
||||
instance: { url: string; token: string };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { GetSession } from '@sveltejs/kit';
|
|||
|
||||
export const getSession: GetSession = async () => {
|
||||
const session: App.Session = {
|
||||
instance: { url: 'https://trans.enby.town', token: 'bweep' }
|
||||
instance: { url: 'https://trans.enby.town', token: '' }
|
||||
};
|
||||
return session;
|
||||
};
|
||||
|
|
|
@ -3,22 +3,27 @@
|
|||
import { now } from '$lib/global-now';
|
||||
import { linkAccount } from '$lib/mastoapi/account';
|
||||
|
||||
import { linkStatus, type MastodonStatus } from '$lib/mastoapi/status';
|
||||
import {
|
||||
linkStatus,
|
||||
type MastodonStatus,
|
||||
type MastodonStatusContext
|
||||
} from '$lib/mastoapi/status';
|
||||
import StatusControls from './StatusControls.svelte';
|
||||
|
||||
export let status: MastodonStatus;
|
||||
export let focused: boolean = false;
|
||||
</script>
|
||||
|
||||
<article class="status">
|
||||
<article class="status" class:focused>
|
||||
<div class="status-content">
|
||||
<aside>
|
||||
<aside class="status-author">
|
||||
<a href={linkAccount(status.account)}>
|
||||
<img class="avatar" alt={status.account.acct} src={status.account.avatar_static} />
|
||||
</a>
|
||||
</aside>
|
||||
|
||||
<div class="status-main">
|
||||
<div class="top-line">
|
||||
<section class="top-line">
|
||||
<a href={linkAccount(status.account)} class="author-link">
|
||||
<strong class="author-display-name">{status.account.display_name}</strong>
|
||||
<span class="author-handle">@{status.account.fqn}</span>
|
||||
|
@ -27,11 +32,21 @@
|
|||
<a class="link-muted" href={linkStatus(status)}>
|
||||
<time>{ago(new Date(status.created_at), $now)}</time>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="status-body">
|
||||
{#if status.spoiler_text}
|
||||
<p>[TODO: content warning <q>{status.spoiler_text}</q>]</p>
|
||||
{/if}
|
||||
|
||||
<p class="status-body">
|
||||
{@html status.content}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="status-attachments">
|
||||
{#each status.media_attachments as attachment}
|
||||
<p>[TODO: attachment <q>{attachment.description || '(no description)'}</q>]</p>
|
||||
{/each}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -44,6 +59,10 @@
|
|||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.status.focused {
|
||||
background-color: var(--col-bg-2);
|
||||
}
|
||||
|
||||
.status-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import type { MastodonStatus, MastodonStatusContext } from './status';
|
||||
|
||||
// Oh jeez
|
||||
|
||||
export function reorderAndFilterPleromaReplies(
|
||||
status: MastodonStatus,
|
||||
context: MastodonStatusContext
|
||||
): MastodonStatusContext {
|
||||
const findInOrig = (id: string): MastodonStatus | undefined =>
|
||||
context.ancestors.find((status) => status.id === id) ||
|
||||
context.descendants.find((status) => status.id === id);
|
||||
|
||||
const newContext: MastodonStatusContext = { ancestors: [], descendants: [] };
|
||||
|
||||
let currParent: MastodonStatus | undefined = status;
|
||||
while (currParent != null) {
|
||||
let replyingTo = currParent.in_reply_to_id;
|
||||
if (replyingTo != null) {
|
||||
currParent = findInOrig(replyingTo);
|
||||
if (currParent != null) {
|
||||
newContext.ancestors.unshift(currParent);
|
||||
}
|
||||
} else {
|
||||
currParent = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const replyTree = new Map<string, MastodonStatus[]>();
|
||||
for (const descendant of context.descendants) {
|
||||
if (descendant.in_reply_to_id == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!replyTree.has(descendant.in_reply_to_id)) {
|
||||
replyTree.set(descendant.in_reply_to_id, []);
|
||||
}
|
||||
replyTree.get(descendant.in_reply_to_id)?.push(descendant);
|
||||
}
|
||||
|
||||
let queue = [status];
|
||||
newContext.descendants.push(status);
|
||||
|
||||
let potentialParent: MastodonStatus | undefined;
|
||||
while ((potentialParent = queue.shift()) != null) {
|
||||
const replies = replyTree.get(potentialParent.id);
|
||||
if (replies) {
|
||||
for (const reply of replies.reverse()) {
|
||||
const parentIndex = newContext.descendants.indexOf(potentialParent);
|
||||
newContext.descendants.splice(parentIndex + 1, 0, reply);
|
||||
}
|
||||
|
||||
for (const reply of replies) {
|
||||
queue.push(reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
newContext.descendants.shift();
|
||||
|
||||
return newContext;
|
||||
}
|
|
@ -1,13 +1,28 @@
|
|||
import type { MastodonAccount } from './account';
|
||||
import type { MastodonObject } from './base';
|
||||
import { reorderAndFilterPleromaReplies } from './pleroma_fixes';
|
||||
|
||||
import { fetchAPI, MastodonAPIError, type InstanceInfo } from './util';
|
||||
|
||||
export interface MastodonMediaAttachment {
|
||||
type: string;
|
||||
url: string;
|
||||
description: string;
|
||||
preview_url: string;
|
||||
}
|
||||
|
||||
export interface MastodonStatus extends MastodonObject {
|
||||
account: MastodonAccount;
|
||||
content: string;
|
||||
spoiler_text: string;
|
||||
url: string;
|
||||
in_reply_to_id: string | null;
|
||||
media_attachments: MastodonMediaAttachment[];
|
||||
}
|
||||
|
||||
export interface MastodonStatusContext {
|
||||
ancestors: MastodonStatus[];
|
||||
descendants: MastodonStatus[];
|
||||
}
|
||||
|
||||
export function linkStatus(status: MastodonStatus) {
|
||||
|
@ -19,3 +34,18 @@ export async function fetchStatus(instance: InstanceInfo, id: string): Promise<M
|
|||
.then((r) => r.json())
|
||||
.then((b) => b as MastodonStatus);
|
||||
}
|
||||
|
||||
export async function fetchStatusContext(
|
||||
instance: InstanceInfo,
|
||||
status: MastodonStatus
|
||||
): Promise<MastodonStatusContext> {
|
||||
const context = await fetchAPI(instance, `/api/v1/statuses/${status.id}/context`)
|
||||
.then((r) => r.json())
|
||||
.then((r) => r as MastodonStatusContext);
|
||||
|
||||
if ('pleroma' in status) {
|
||||
return reorderAndFilterPleromaReplies(status, context);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export interface InstanceInfo {
|
||||
url: string;
|
||||
token?: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export class MastodonAPIError extends Error {
|
||||
|
@ -24,7 +24,7 @@ export async function fetchAPI(
|
|||
const opts = { ...options };
|
||||
|
||||
if (opts.headers == null) opts.headers = {};
|
||||
if (instance.token != null) {
|
||||
if (isLoggedIn(instance)) {
|
||||
opts.headers['Authorization'] = instance.token;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
<script lang="ts" context="module">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { fetchStatus, type MastodonStatus } from '$lib/mastoapi/status';
|
||||
import {
|
||||
fetchStatus,
|
||||
fetchStatusContext,
|
||||
type MastodonStatus,
|
||||
type MastodonStatusContext
|
||||
} from '$lib/mastoapi/status';
|
||||
|
||||
export const load: Load = async ({ session, params }) => {
|
||||
try {
|
||||
const status = await fetchStatus(session.instance, params.id);
|
||||
return { props: { status } };
|
||||
let contextPromise = await fetchStatusContext(session.instance, status);
|
||||
return { props: { status, contextPromise } };
|
||||
} catch (err) {
|
||||
return { status: 404, error: err as Error };
|
||||
}
|
||||
|
@ -15,10 +21,27 @@
|
|||
<script lang="ts">
|
||||
import Status from '$lib/components/Status.svelte';
|
||||
export let status: MastodonStatus;
|
||||
export let contextPromise: Promise<MastodonStatusContext>;
|
||||
</script>
|
||||
|
||||
{#await contextPromise then context}
|
||||
<div class="ancestors">
|
||||
{#each context.ancestors as ancestor}
|
||||
<Status status={ancestor} />
|
||||
{/each}
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
{#if status != null}
|
||||
<Status {status} />
|
||||
<Status {status} focused={true} />
|
||||
{:else}
|
||||
<p>a</p>
|
||||
{/if}
|
||||
|
||||
{#await contextPromise then context}
|
||||
<div class="descendants">
|
||||
{#each context.descendants as descendant}
|
||||
<Status status={descendant} />
|
||||
{/each}
|
||||
</div>
|
||||
{/await}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
:root {
|
||||
--col-bg-0: #18161d;
|
||||
--col-bg-1: #221e28;
|
||||
|
||||
--col-bg-2: #30293a;
|
||||
|
||||
--col-fg-0: #f2ecff;
|
||||
--col-fg-1: #ae96cc;
|
||||
--col-fg-2: #85739a;
|
||||
|
|
Loading…
Reference in New Issue