make upvotes work

This commit is contained in:
Yusur 2025-10-23 11:08:14 +02:00
parent 248555f7ad
commit 11deee34a7
4 changed files with 93 additions and 25 deletions

View file

@ -1,25 +1,62 @@
<script lang="ts"> <script lang="ts">
import { DateTime } from "luxon";
import { RiHashtag, RiHistoryLine, RiUserLine } from "svelte-remixicon";
import type { PostEntry } from "./backend"; import type { PostEntry } from "./backend";
import PostMeta from "./PostMeta.svelte"; import PostMeta from "./PostMeta.svelte";
import { SvelteShowdown } from "svelte-showdown"; import { SvelteShowdown } from "svelte-showdown";
import VoteButton from "./VoteButton.svelte";
let { post }: { post: PostEntry } = $props(); let { post }: { post: PostEntry } = $props();
let { id, title, created_at, content } = post; let { id, title, content = "", votes, my_vote } = post;
</script> </script>
<card class="post-frame"> <article class="card">
<h3 class="post-title"> <div class="post-frame">
<a href="/={id}">{title}</a> <h3 class="post-title">
</h3> <a href="/={id}">{title}</a>
<PostMeta {post} /> </h3>
<!-- TODO pist content --> <PostMeta {post} />
<div class="post-content shorten"> <div class="post-content shorten">
<SvelteShowdown content={ content || "" } /> <SvelteShowdown { content } />
</div>
<aside class="message-stats">
<VoteButton score={votes} vote={my_vote} {id} />
</aside>
</div> </div>
</card> </article>
<style> <style>
.post-frame {
padding-left: 2em;
position: relative;
}
.message-stats {
position: absolute;
inset-inline-start: 0;
top: 0;
width: 2em;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.shorten {
max-height: 18em;
overflow-y: hidden;
position: relative;
}
.shorten::after {
content: '';
position: absolute;
z-index: 10;
top: 16em;
left: 0;
width: 100%;
height: 2em;
display: block;
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, var(--background) 100%);
}
</style> </style>

View file

@ -18,7 +18,7 @@
let { post }: { post: PostEntry } = $props(); let { post }: { post: PostEntry } = $props();
let me = getMe(); let me = getMe();
let { title, created_at, id, content = '', to } = post; let { title, id, content = '', to, votes, my_vote } = post;
</script> </script>
<SLayout title={to.display_name + (to.type === 'guild' ? ` (+${to.name})` : to.type === 'user' ? ` (@${to.username})` : '')}> <SLayout title={to.display_name + (to.type === 'guild' ? ` (+${to.name})` : to.type === 'user' ? ` (@${to.username})` : '')}>
@ -34,10 +34,10 @@ let { title, created_at, id, content = '', to } = post;
<!-- content, formatted as markdown --> <!-- content, formatted as markdown -->
</div> </div>
</div><!-- .post-body --> </div><!-- .post-body -->
<div class="message-stats"> <aside class="message-stats">
<!-- upvotes / downvotes --> <!-- upvotes / downvotes -->
<VoteButton /> <VoteButton score={votes} vote={my_vote} {id} />
</div> </aside>
<ul class="message-options row"> <ul class="message-options row">
{#if me && me.id !== post.author?.id} {#if me && me.id !== post.author?.id}
<li><a href="/report/post/{id}"><RiFlagLine/> Report</a></li> <li><a href="/report/post/{id}"><RiFlagLine/> Report</a></li>

View file

@ -1,31 +1,46 @@
<script lang="ts"> <script lang="ts">
import { RiHeartFill, RiHeartLine, RiThumbDownFill, RiThumbDownLine } from "svelte-remixicon"; import { RiHeartFill, RiHeartLine, RiThumbDownFill, RiThumbDownLine } from "svelte-remixicon";
import { backend } from "./backend";
let vote = $state(0); let { score = $bindable(null), vote = $bindable(0), id } : { score?: number | null, vote?: 0 | 1 | -1, id: string } = $props();
let { score } : { score?: number | null } = $props();
async function castVote(v: 0 | 1 | -1) {
let readyBackend = await backend.withEvent(null).oath();
let result = await readyBackend.submitJson(`post/${id}/upvote`, {
vote: v
});
if (score === null) { return; }
if (result.status >= 400) {
// TODO toast error?
console.error("error:", (await result.json()));
return;
}
let {votes} = await result.json();
vote = v;
score = votes;
}
</script> </script>
<div class="upvote-button"> <div class="upvote-button">
{#if vote > 0} {#if vote > 0}
<button class="inline"> <button class="inline up" onclick={() => { castVote(0).then(() => {}); }}>
<RiHeartFill /> <RiHeartFill />
</button> </button>
{:else} {:else}
<button class="inline"> <button class="inline" onclick={() => { castVote(1).then(() => {}); }}>
<RiHeartLine /> <RiHeartLine />
</button> </button>
{/if} {/if}
<strong>{score ?? '-'}</strong> <strong>{score ?? '-'}</strong>
{#if vote > 0} {#if vote < 0}
<button class="inline"> <button class="inline down" onclick={() => { castVote(0).then(() => {}); }}>
<RiThumbDownFill /> <RiThumbDownFill />
</button> </button>
{:else} {:else}
<button class="inline"> <button class="inline" onclick={() => { castVote(-1).then(() => {}); }}>
<RiThumbDownLine /> <RiThumbDownLine />
</button> </button>
{/if} {/if}
@ -37,5 +52,18 @@ let { score } : { score?: number | null } = $props();
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
button.inline {
color: var(--border);
}
button.inline.up {
color: var(--accent);
}
button.inline.down {
color: var(--c11-accent);
}
:global(.color-theme-11) button.inline.down, :global(.color-theme-9) button.inline.down {
color: var(--c14-accent);
}
</style> </style>

View file

@ -31,7 +31,10 @@ export type PostEntry = {
author?: UserEntry | null, author?: UserEntry | null,
content?: string, content?: string,
to: UserEntry | GuildEntry , to: UserEntry | GuildEntry ,
privacy?: number privacy?: number,
votes?: number | null,
my_vote?: 1 | -1 | 0,
comment_count?: number | null
}; };
export type ServerHealth = { export type ServerHealth = {