Compare commits
No commits in common. "80ecfd67471527843e29329cbd51d85c923427a3" and "54cafaa1da0a93fe12f6c5863725abe3ce90934f" have entirely different histories.
80ecfd6747
...
54cafaa1da
23 changed files with 85 additions and 376 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@yusurko/vigil",
|
"name": "@yusurko/vigil",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0-dev43",
|
"version": "0.1.0-dev42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|
|
||||||
2
src/app.d.ts
vendored
2
src/app.d.ts
vendored
|
|
@ -14,7 +14,7 @@ declare global {
|
||||||
site: ServerHealth | null,
|
site: ServerHealth | null,
|
||||||
me: UserEntry | null,
|
me: UserEntry | null,
|
||||||
results?: object[],
|
results?: object[],
|
||||||
query?: string,
|
query?: string
|
||||||
}
|
}
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { RiChat3Line } from "svelte-remixicon";
|
|
||||||
|
|
||||||
|
|
||||||
let { count } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="comment-count">
|
|
||||||
<RiChat3Line />
|
|
||||||
<strong>{count??'-'}</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.comment-count {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-count :global(svg) {
|
|
||||||
color: var(--border);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { CommentEntry } from "./backend";
|
|
||||||
import Centered from "./Centered.svelte";
|
|
||||||
|
|
||||||
|
|
||||||
let comments: null | CommentEntry = $state(null);
|
|
||||||
|
|
||||||
let { post } = $props();
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if comments === null}
|
|
||||||
<Centered>
|
|
||||||
<button class="inline">Show comments</button>
|
|
||||||
</Centered>
|
|
||||||
{/if}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import PrivacySelect from "./PrivacySelect.svelte";
|
|
||||||
|
|
||||||
let { content = $bindable(""), privacy = $bindable(0) } = $props();
|
|
||||||
|
|
||||||
let contentLength = $derived(content.length);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<input type="text" name="title" maxlength=256 placeholder="An interesting title"/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<textarea bind:value={content}></textarea>
|
|
||||||
<output><small class="faint">{contentLength} chars</small></output>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PrivacySelect bind:value={privacy} />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
background-color: inherit;
|
|
||||||
color: var(--text-primary);
|
|
||||||
min-height: 10em;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[name="title"] {
|
|
||||||
width: 100%;
|
|
||||||
margin: 6px 0;
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,68 +1,25 @@
|
||||||
<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";
|
|
||||||
import CommentCount from "./CommentCount.svelte";
|
|
||||||
|
|
||||||
let { post }: { post: PostEntry } = $props();
|
let { post }: { post: PostEntry } = $props();
|
||||||
let { id, title, content = "", votes, my_vote, comment_count } = post;
|
let { id, title, created_at, content } = post;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<article class="card">
|
<card class="post-frame">
|
||||||
<div class="post-frame">
|
<h3 class="post-title">
|
||||||
<h3 class="post-title">
|
<a href="/={id}">{title}</a>
|
||||||
<a href="/={id}">{title}</a>
|
</h3>
|
||||||
</h3>
|
<PostMeta {post} />
|
||||||
<PostMeta {post} />
|
<!-- TODO pist content -->
|
||||||
<div class="post-content shorten">
|
<div class="post-content shorten">
|
||||||
<SvelteShowdown { content } />
|
<SvelteShowdown content={ content || "" } />
|
||||||
</div>
|
|
||||||
<aside class="message-stats">
|
|
||||||
<VoteButton score={votes} vote={my_vote} {id} />
|
|
||||||
<CommentCount count={comment_count} />
|
|
||||||
</aside>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</card>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.post-frame {
|
|
||||||
padding-inline-start: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title {
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RiEditLine, RiFlagLine } from "svelte-remixicon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
import { RiEditLine, RiFlagLine, RiHistoryLine, RiHome2Line, RiUserLine } from "svelte-remixicon";
|
||||||
import type { PostEntry } from "./backend";
|
import type { PostEntry } from "./backend";
|
||||||
import SLayout from "./SLayout.svelte";
|
import SLayout from "./SLayout.svelte";
|
||||||
import GuildAbout from "./GuildAbout.svelte";
|
import GuildAbout from "./GuildAbout.svelte";
|
||||||
|
|
@ -10,15 +12,13 @@
|
||||||
import { SvelteShowdown } from "svelte-showdown";
|
import { SvelteShowdown } from "svelte-showdown";
|
||||||
import VoteButton from "./VoteButton.svelte";
|
import VoteButton from "./VoteButton.svelte";
|
||||||
import { getMe } from "./globals.svelte";
|
import { getMe } from "./globals.svelte";
|
||||||
import CommentCount from "./CommentCount.svelte";
|
|
||||||
import CommentSection from "./CommentSection.svelte";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let { post }: { post: PostEntry } = $props();
|
let { post }: { post: PostEntry } = $props();
|
||||||
|
|
||||||
let me = getMe();
|
let me = getMe();
|
||||||
let { title, id, content = '', to, votes, my_vote, comment_count } = post;
|
let { title, created_at, id, content = '', to } = 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,24 +34,21 @@ let { title, id, content = '', to, votes, my_vote, comment_count } = post;
|
||||||
<!-- content, formatted as markdown -->
|
<!-- content, formatted as markdown -->
|
||||||
</div>
|
</div>
|
||||||
</div><!-- .post-body -->
|
</div><!-- .post-body -->
|
||||||
<aside class="message-stats">
|
<div class="message-stats">
|
||||||
<!-- upvotes / downvotes -->
|
<!-- upvotes / downvotes -->
|
||||||
<VoteButton score={votes} vote={my_vote} {id} />
|
<VoteButton />
|
||||||
<CommentCount count={comment_count} />
|
</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>
|
||||||
{/if}
|
{/if}
|
||||||
{#if me && me.id === post.author?.id }
|
{#if me && me.id === post.author?.id }
|
||||||
<li><a href="/edit/={id}"><RiEditLine/> Edit</a></li>
|
<li><a href="/edit/post/{id}"><RiEditLine/> Edit</a></li>
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<CommentSection {post} />
|
|
||||||
|
|
||||||
{#snippet left()}
|
{#snippet left()}
|
||||||
{#if to.type === 'guild'}
|
{#if to.type === 'guild'}
|
||||||
<GuildMenu guild={to} />
|
<GuildMenu guild={to} />
|
||||||
|
|
@ -77,13 +74,13 @@ let { title, id, content = '', to, votes, my_vote, comment_count } = post;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
.post-body {
|
.post-body {
|
||||||
margin-inline-start: 2em;
|
margin-inline-start: 3em;
|
||||||
}
|
}
|
||||||
.message-stats {
|
.message-stats {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset-inline-start: 0;
|
inset-inline-start: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 2em;
|
width: 3em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ let { guild }: { guild: GuildEntry } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{#if guild}
|
{#if guild}
|
||||||
<AsideCard title={'About ' + (guild.display_name? `${guild.display_name} (+${guild.name})`: `+${guild.name}`)}>
|
<AsideCard title={'About ' + (guild.display_name? `${guild.display_name} (+${guild.name})`: `+${guild.name}`)}>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { backend, type GuildEntry } from "./backend";
|
import { backend, type GuildEntry } from "./backend";
|
||||||
|
|
||||||
|
|
||||||
let { value = $bindable("") } = $props();
|
let value = $state("");
|
||||||
|
|
||||||
let suggestions: Promise<GuildEntry[]> = $derived(getSuggestions(value));
|
let suggestions: Promise<GuildEntry[]> = $derived(getSuggestions(value));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { RiAddLine, RiLoginBoxLine, RiLogoutBoxLine, RiSearch2Line, RiSearchLine, RiSettings3Line, RiShieldStarLine, RiUserLine } from "svelte-remixicon";
|
import { RiLoginBoxLine, RiLogoutBoxLine, RiSearch2Line, RiSearchLine, RiSettings3Line } from "svelte-remixicon";
|
||||||
import { activePostCount } from "./globals.svelte";
|
import { activePostCount } from "./globals.svelte";
|
||||||
import type { UserEntry } from "./backend";
|
import type { UserEntry } from "./backend";
|
||||||
|
|
||||||
|
|
@ -35,23 +35,7 @@ let enable_search = $derived(user !== null);
|
||||||
<span><a href="/@{user.username}">@{user.username}</a></span>
|
<span><a href="/@{user.username}">@{user.username}</a></span>
|
||||||
<span>{user.karma || 0} karma</span>
|
<span>{user.karma || 0} karma</span>
|
||||||
</div>
|
</div>
|
||||||
<a class="mobileonly" href="/@{user.username}">
|
|
||||||
<RiUserLine />
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="nomobile">
|
|
||||||
<a href="/create">
|
|
||||||
<button>Create post</button>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{#if user.badges && user.badges.indexOf("administrator") >= 0}
|
|
||||||
<li>
|
|
||||||
<a href="/admin/">
|
|
||||||
<RiShieldStarLine />
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<li><a href="/logout" aria-label="Log out" title="Log out"><RiLogoutBoxLine /></a></li>
|
<li><a href="/logout" aria-label="Log out" title="Log out"><RiLogoutBoxLine /></a></li>
|
||||||
{:else}
|
{:else}
|
||||||
<li><a href="/login" aria-label="Log in" title="Log in"><RiLoginBoxLine /></a></li>
|
<li><a href="/login" aria-label="Log in" title="Log in"><RiLoginBoxLine /></a></li>
|
||||||
|
|
@ -114,9 +98,7 @@ let enable_search = $derived(user !== null);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { RiAddCircleLine, RiChat2Line, RiCompassDiscoverLine, RiHome2Line, RiHomeLine, RiNotificationLine, RiUserLine } from "svelte-remixicon";
|
import { RiAddCircleLine, RiChat2Line, RiCompassDiscoverLine, RiHome2Line, RiHomeLine, RiNotificationLine } from "svelte-remixicon";
|
||||||
import { getMe } from "./globals.svelte";
|
|
||||||
|
|
||||||
function rickroll (){
|
function rickroll (){
|
||||||
if (browser) {
|
if (browser) {
|
||||||
window.open( "https://youtu.be/dQw4w9WgXcQ" );
|
window.open( "https://youtu.be/dQw4w9WgXcQ" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let me = $derived(getMe());
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if me}
|
|
||||||
<footer class="mobileonly">
|
<footer class="mobileonly">
|
||||||
<ul class="row">
|
<ul class="row">
|
||||||
<li><a href="/" title="Homepage"><RiHome2Line size="2em" /></a></li>
|
<li><a href="/" title="Homepage"><RiHome2Line size="2em" /></a></li>
|
||||||
<li><a href="/" title="Discover"><RiCompassDiscoverLine size="2em" /></a></li>
|
<li><a href="/" title="Discover"><RiCompassDiscoverLine size="2em" /></a></li>
|
||||||
<li><a href="/create" title="Create"><RiAddCircleLine size="2em" /></a></li>
|
<li><a href="/new" title="Create"><RiAddCircleLine size="2em" /></a></li>
|
||||||
<li><a href="/@{me.username}" title="Profile"><RiUserLine size="2em" /></a></li>
|
<li><a href="/user/yusur" title="Messages"><RiChat2Line size="2em" /></a></li>
|
||||||
<li><a href="?" onclick={rickroll} title="Notifications"><RiNotificationLine size="2em" /></a></li>
|
<li><a href="?" onclick={rickroll} title="Notifications"><RiNotificationLine size="2em" /></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,31 @@
|
||||||
<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 { score = $bindable(null), vote = $bindable(0), id } : { score?: number | null, vote?: 0 | 1 | -1, id: string } = $props();
|
let vote = $state(0);
|
||||||
|
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 up" onclick={() => { castVote(0).then(() => {}); }}>
|
<button class="inline">
|
||||||
<RiHeartFill />
|
<RiHeartFill />
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="inline" onclick={() => { castVote(1).then(() => {}); }}>
|
<button class="inline">
|
||||||
<RiHeartLine />
|
<RiHeartLine />
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<strong>{score ?? '-'}</strong>
|
<strong>{score ?? '-'}</strong>
|
||||||
|
|
||||||
{#if vote < 0}
|
{#if vote > 0}
|
||||||
<button class="inline down" onclick={() => { castVote(0).then(() => {}); }}>
|
<button class="inline">
|
||||||
<RiThumbDownFill />
|
<RiThumbDownFill />
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="inline" onclick={() => { castVote(-1).then(() => {}); }}>
|
<button class="inline">
|
||||||
<RiThumbDownLine />
|
<RiThumbDownLine />
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -52,18 +37,5 @@ async function castVote(v: 0 | 1 | -1) {
|
||||||
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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { RiBarricadeLine } from "svelte-remixicon";
|
|
||||||
import Centered from "./Centered.svelte";
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<Centered>
|
|
||||||
<faint><RiBarricadeLine /></faint>
|
|
||||||
</Centered>
|
|
||||||
|
|
@ -29,21 +29,8 @@ export type PostEntry = {
|
||||||
title: string,
|
title: string,
|
||||||
created_at: string,
|
created_at: string,
|
||||||
author?: UserEntry | null,
|
author?: UserEntry | null,
|
||||||
content?: string,
|
content?: string | null,
|
||||||
to: UserEntry | GuildEntry ,
|
to: UserEntry | GuildEntry
|
||||||
privacy?: number,
|
|
||||||
votes?: number | null,
|
|
||||||
my_vote?: 1 | -1 | 0,
|
|
||||||
comment_count?: number | null
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CommentEntry = {
|
|
||||||
id: string,
|
|
||||||
parent?: {id: string},
|
|
||||||
locked?: boolean,
|
|
||||||
removed?: number | true,
|
|
||||||
content?: string,
|
|
||||||
created_at: string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ServerHealth = {
|
export type ServerHealth = {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ export function setHealth ({ name, version, post_count, user_count, color_theme
|
||||||
health.color_theme = color_theme;
|
health.color_theme = color_theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function setMe (me: UserEntry | null) {
|
export function setMe (me: UserEntry | null) {
|
||||||
health.me = me;
|
health.me = me;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ let colorThemeCls = $derived(`color-scheme-${colorScheme} color-theme-${colorThe
|
||||||
<!-- end SEO tags -->
|
<!-- end SEO tags -->
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="{colorThemeCls} contents">
|
<global-wrapper class={colorThemeCls}>
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/">{appName()}</a>
|
<a href="/">{appName()}</a>
|
||||||
|
|
@ -54,7 +54,7 @@ let colorThemeCls = $derived(`color-scheme-${colorScheme} color-theme-${colorThe
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<MobileFooter />
|
<MobileFooter />
|
||||||
</div>
|
</global-wrapper>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -88,13 +88,8 @@ let colorThemeCls = $derived(`color-scheme-${colorScheme} color-theme-${colorThe
|
||||||
|
|
||||||
@media screen and (max-width: 799px) {
|
@media screen and (max-width: 799px) {
|
||||||
main {
|
main {
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.contents {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Centered from "$lib/Centered.svelte";
|
import Centered from "$lib/Centered.svelte";
|
||||||
import EditPost from "$lib/EditPost.svelte";
|
|
||||||
import { getMe } from "$lib/globals.svelte";
|
import { getMe } from "$lib/globals.svelte";
|
||||||
import GuildSelect from "$lib/GuildSelect.svelte";
|
import GuildSelect from "$lib/GuildSelect.svelte";
|
||||||
|
import PrivacySelect from "$lib/PrivacySelect.svelte";
|
||||||
import SLayout from "$lib/SLayout.svelte";
|
import SLayout from "$lib/SLayout.svelte";
|
||||||
import Wip from "$lib/Wip.svelte";
|
|
||||||
import { RiErrorWarningLine } from "svelte-remixicon";
|
import { RiErrorWarningLine } from "svelte-remixicon";
|
||||||
|
|
||||||
let me = getMe();
|
let me = getMe();
|
||||||
|
|
||||||
let content = $state("");
|
let content = $state("");
|
||||||
let privacy = $state(0);
|
let privacy = $state(0);
|
||||||
let guildName = $state("");
|
|
||||||
|
|
||||||
let enablePost = $derived(!!content && !!guildName && false);
|
let contentLength = $derived(content.length);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,26 +22,34 @@
|
||||||
<p>Posting as <strong>@{me.username}</strong></p>
|
<p>Posting as <strong>@{me.username}</strong></p>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Post to:
|
Post to: <!-- TODO autocomplete! -->
|
||||||
<GuildSelect bind:value={guildName} />
|
<GuildSelect />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<EditPost bind:content bind:privacy />
|
<label>
|
||||||
|
<input type="text" name="title" maxlength=256 placeholder="An interesting title"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<textarea bind:value={content}></textarea>
|
||||||
|
<output><small class="faint">{contentLength} chars</small></output>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<PrivacySelect bind:value={privacy} />
|
||||||
{#if privacy === 0}
|
{#if privacy === 0}
|
||||||
<span class="warning"><RiErrorWarningLine /> Your post will be PUBLIC!</span>
|
<span class="warning"><RiErrorWarningLine /> Your post will be PUBLIC!</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button class="card primary" disabled={!enablePost}>Create</button>
|
<button class="card primary" disabled>Create</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#snippet left()}
|
{#snippet left()}
|
||||||
<Wip />
|
...
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet right()}
|
{#snippet right()}
|
||||||
<Wip />
|
...
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</SLayout>
|
</SLayout>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -53,10 +60,22 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
background-color: inherit;
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 10em;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[name="title"] {
|
||||||
|
width: 100%;
|
||||||
|
margin: 6px 0;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { PostEntry } from "$lib/backend";
|
|
||||||
import Centered from "$lib/Centered.svelte";
|
|
||||||
import EditPost from "$lib/EditPost.svelte";
|
|
||||||
import { getMe } from "$lib/globals.svelte";
|
|
||||||
import GuildSelect from "$lib/GuildSelect.svelte";
|
|
||||||
import SLayout from "$lib/SLayout.svelte";
|
|
||||||
import Wip from "$lib/Wip.svelte";
|
|
||||||
import { RiErrorWarningLine } from "svelte-remixicon";
|
|
||||||
|
|
||||||
let me = getMe();
|
|
||||||
|
|
||||||
let { data }: { data: PostEntry } = $props();
|
|
||||||
let post = $state(data) ;
|
|
||||||
|
|
||||||
let { content = "", privacy } = $derived(post);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
{#if me?.id === post.author?.id}
|
|
||||||
<SLayout title="New post">
|
|
||||||
<form method="POST" class="card">
|
|
||||||
<p>Posting as <strong>@{me?.username}</strong></p>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Post to:
|
|
||||||
<GuildSelect />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<EditPost bind:content bind:privacy />
|
|
||||||
|
|
||||||
{#if privacy === 0}
|
|
||||||
<span class="warning"><RiErrorWarningLine /> Your post will be PUBLIC!</span>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<button class="card primary" disabled>Create</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{#snippet left()}
|
|
||||||
<Wip />
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#snippet right()}
|
|
||||||
<Wip />
|
|
||||||
{/snippet}
|
|
||||||
</SLayout>
|
|
||||||
{:else if me}
|
|
||||||
You can't edit posts that are not your own.
|
|
||||||
{:else}
|
|
||||||
<Centered>
|
|
||||||
You must be <a href="login">logged in</a> in order to edit your own posts.
|
|
||||||
</Centered>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import { backend, type GuildEntry } from '$lib/backend.js';
|
|
||||||
import { getMe } from '$lib/globals.svelte';
|
|
||||||
import { error, isHttpError, redirect } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
export async function load(event) {
|
|
||||||
|
|
||||||
const { params } = event;
|
|
||||||
const { id } = params;
|
|
||||||
|
|
||||||
const resp = await backend.withEvent(event).fetch('post/' + encodeURIComponent(id));
|
|
||||||
|
|
||||||
if(resp.status === 404) {
|
|
||||||
error(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
let post;
|
|
||||||
|
|
||||||
try{
|
|
||||||
const respJ = await resp.json();
|
|
||||||
|
|
||||||
let { posts } = respJ;
|
|
||||||
post = posts[id];
|
|
||||||
|
|
||||||
let me = getMe();
|
|
||||||
|
|
||||||
if (!me) {
|
|
||||||
redirect(303, "/login?next=" + encodeURIComponent(event.url.pathname));
|
|
||||||
}
|
|
||||||
if ( post.author.id !== me?.id) {
|
|
||||||
error(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (post?.to && post.to.type === 'guild') {
|
|
||||||
const guild: GuildEntry = post.to;
|
|
||||||
const guildResp = await backend.withEvent(event).fetch('guild/' + encodeURIComponent(guild.id));
|
|
||||||
const guildJson = await guildResp.json();
|
|
||||||
const guildInfo = guildJson?.guilds?.[guild.id];
|
|
||||||
guildInfo.type = 'guild';
|
|
||||||
post.to = guildInfo || guild;
|
|
||||||
console.log(post.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
if (isHttpError(e)) throw e;
|
|
||||||
|
|
||||||
console.error(e);
|
|
||||||
error(502);
|
|
||||||
}
|
|
||||||
|
|
||||||
return post;
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { redirect } from 'sveltekit-flash-message/server';
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: async (event) => {
|
default: async (event) => {
|
||||||
|
// TODO login
|
||||||
|
|
||||||
const { request } = event;
|
const { request } = event;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from "$app/forms";
|
import { enhance } from "$app/forms";
|
||||||
import Centered from "$lib/Centered.svelte";
|
import Centered from "$lib/Centered.svelte";
|
||||||
import Wip from "$lib/Wip.svelte";
|
|
||||||
import { RiInformationLine, RiUserLine } from "svelte-remixicon";
|
import { RiInformationLine, RiUserLine } from "svelte-remixicon";
|
||||||
|
|
||||||
let username = $state("");
|
let username = $state("");
|
||||||
|
|
@ -26,7 +25,7 @@ let disabled = $derived(username.length < 2 || username.length > 30 ||
|
||||||
<small class="faint">Must be a working e-mail address <abbr title="Will be used for password recovery and important communications"><RiInformationLine /></abbr></small>
|
<small class="faint">Must be a working e-mail address <abbr title="Will be used for password recovery and important communications"><RiInformationLine /></abbr></small>
|
||||||
<input type="text" name="username" bind:value={email} />
|
<input type="text" name="username" bind:value={email} />
|
||||||
</label>
|
</label>
|
||||||
<Wip />
|
...
|
||||||
|
|
||||||
<button class="primary" disabled={disabled}>Sign up</button>
|
<button class="primary" disabled={disabled}>Sign up</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,14 @@ export const actions = {
|
||||||
event.locals.results = results;
|
event.locals.results = results;
|
||||||
event.locals.query = query;
|
event.locals.query = query;
|
||||||
console.log(event.locals);
|
console.log(event.locals);
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|
||||||
export function load (event) {
|
export async function load (event) {
|
||||||
const { results, query } = event.locals;
|
const { results, query } = event.locals;
|
||||||
|
|
||||||
return { results, query };
|
console.log({ results, query });
|
||||||
|
return { results, query };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
import type { PageProps } from "./$types";
|
import type { PageProps } from "./$types";
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
console.log(data);
|
|
||||||
let { query, results } = $derived(data);
|
let { query, results } = $derived(data);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue