Skip to content

Commit

Permalink
Add comment threads
Browse files Browse the repository at this point in the history
  • Loading branch information
rudolfs committed Oct 16, 2024
1 parent f0f8228 commit 3303a33
Show file tree
Hide file tree
Showing 17 changed files with 1,291 additions and 79 deletions.
3 changes: 1 addition & 2 deletions src/components/Border.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
export let styleHeight: string | undefined = undefined;
export let styleMinHeight: string | undefined = undefined;
export let styleWidth: string | undefined = undefined;
export let styleCursor: "default" | "pointer" = "default";
export let styleCursor: "default" | "pointer" | "text" = "default";
export let styleGap: string = "0.5rem";
$: style =
Expand Down Expand Up @@ -128,7 +128,6 @@
"p3-1 p3-2 p3-3 p3-4 p3-5"
"p4-1 p4-2 p4-3 p4-4 p4-5"
"p5-1 p5-2 p5-3 p5-4 p5-5";
overflow: hidden;
}
.container .p2-3,
.container .p3-2,
Expand Down
207 changes: 207 additions & 0 deletions src/components/Comment.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<script lang="ts">
import type { Author } from "@bindings/Author";
import type { Comment } from "@bindings/Comment";
import type { Edit } from "@bindings/Edit";
import type { Reaction } from "@bindings/Reaction";
type Embed = Comment["embeds"][0];
import { tick } from "svelte";
import { closeFocused } from "./Popover.svelte";
import * as utils from "@app/lib/utils";
import ExtendedTextarea from "@app/components/ExtendedTextarea.svelte";
import Icon from "@app/components/Icon.svelte";
import Id from "@app/components/Id.svelte";
import Markdown from "@app/components/Markdown.svelte";
import NodeId from "@app/components/NodeId.svelte";
import ReactionSelector from "@app/components/ReactionSelector.svelte";
import Reactions from "@app/components/Reactions.svelte";
export let id: string | undefined = undefined;
export let rid: string;
export let author: Author;
export let body: string;
export let enableAttachments: boolean = false;
export let reactions: Reaction[] | undefined = undefined;
export let embeds: Map<string, Embed> | undefined = undefined;
export let caption = "commented";
export let timestamp: number;
export let lastEdit: Edit | undefined = undefined;
export let disallowEmptyBody: boolean = false;
export let editComment:
| ((body: string, embeds: Comment["embeds"]) => Promise<void>)
| undefined = undefined;
export let reactOnComment:
| ((authors: Author[], reaction: string) => Promise<void>)
| undefined = undefined;
let state: "read" | "edit" | "submit" = "read";
</script>

<style>
.card {
display: flex;
flex-direction: column;
padding: 0.5rem 0;
gap: 0.5rem;
}
.card-header {
display: flex;
align-items: center;
white-space: nowrap;
flex-wrap: wrap;
padding: 0 0.75rem;
min-height: 1.5rem;
gap: 0.5rem;
font-size: var(--font-size-small);
}
.card-metadata {
color: var(--color-fill-gray);
font-size: var(--font-size-small);
}
.header-right {
display: flex;
margin-left: auto;
gap: 0.5rem;
}
.card-body {
display: flex;
align-items: center;
min-height: 1.625rem;
word-wrap: break-word;
font-size: var(--font-size-small);
padding: 0 1rem;
}
.actions {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
.timestamp {
font-size: var(--font-size-small);
color: var(--color-fill-gray);
}
.edit-buttons {
display: flex;
gap: 0.25rem;
}
</style>

<div class="card" {id}>
<div style:position="relative">
<div class="card-header">
<slot class="icon" name="icon" />
<NodeId {...utils.authorForNodeId(author)} />
<slot name="caption">{caption}</slot>
{#if id}
<Id {id} />
{/if}
<span class="timestamp" title={utils.absoluteTimestamp(timestamp)}>
{utils.formatTimestamp(timestamp)}
</span>
{#if lastEdit}
<div
class="card-metadata"
title={utils.formatEditedCaption(
lastEdit.author,
lastEdit.timestamp,
)}>
• edited
</div>
{/if}
<div class="header-right">
{#if id && editComment}
<div class="edit-buttons">
<Icon
styleCursor="pointer"
name="pen"
onclick={() =>
state === "edit" ? (state = "read") : (state = "edit")} />
</div>
{/if}
{#if id && reactions && reactions.length === 0 && reactOnComment}
{@const reactOnComment_ = reactOnComment}
<div class="actions">
<ReactionSelector
popoverPositionRight="0"
popoverPositionBottom="1.5rem"
{reactions}
on:select={async ({ detail: { authors, emoji } }) => {
try {
await reactOnComment_(authors, emoji);
} finally {
closeFocused();
}
}} />
</div>
{/if}
<slot name="actions" />
</div>
</div>
</div>

{#if body.trim() === "" && state === "read"}
<div class="card-body">
<span class="txt-missing txt-small" style:line-height="1.625rem">
No description.
</span>
</div>
{:else}
<div class="card-body">
{#if editComment && state !== "read"}
{@const editComment_ = editComment}
<ExtendedTextarea
{body}
{rid}
{embeds}
{enableAttachments}
{disallowEmptyBody}
submitInProgress={state === "submit"}
submitCaption="Save"
placeholder="Leave a comment"
on:submit={async ({ detail: { comment, embeds } }) => {
state = "submit";
try {
await editComment_(comment, Array.from(embeds.values()));
} finally {
state = "read";
}
}}
on:close={async () => {
body = body;
await tick();
state = "read";
}} />
{:else}
<div style:width="100%">
<div style:overflow="hidden">
<Markdown {rid} breaks content={body} />
</div>
{#if id && reactions && reactions.length > 0}
<div class="actions">
{#if id && reactOnComment}
{@const reactOnComment_ = reactOnComment}
<ReactionSelector
popoverPositionLeft="0"
popoverPositionBottom="1.5rem"
{reactions}
on:select={async ({ detail: { authors, emoji } }) => {
try {
await reactOnComment_(authors, emoji);
} finally {
closeFocused();
}
}} />
{/if}
<Reactions handleReaction={reactOnComment} {reactions} />
</div>
{/if}
</div>
{/if}
</div>
{/if}
</div>
79 changes: 79 additions & 0 deletions src/components/CommentToggleInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script lang="ts">
import type { Comment } from "@bindings/Comment";
import ExtendedTextarea from "@app/components/ExtendedTextarea.svelte";
import Border from "./Border.svelte";
export let rid: string;
export let body: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
export let submitCaption: string | undefined = undefined;
export let enableAttachments: boolean = false;
export let inline: boolean = false;
export let focus: boolean = false;
export let submit: (
comment: string,
embeds: Comment["embeds"][0][],
) => Promise<void>;
export let onclose: (() => void) | undefined = undefined;
export let onexpand: (() => void) | undefined = undefined;
export let disallowEmptyBody: boolean = false;
let state: "collapsed" | "expanded" | "submit";
$: state = onclose !== undefined ? "expanded" : "collapsed";
</script>

<style>
.inactive {
padding: 0 0.75rem;
font-size: var(--font-size-small);
color: var(--color-fill-gray);
}
</style>

{#if state !== "collapsed"}
<ExtendedTextarea
{disallowEmptyBody}
{rid}
{inline}
{placeholder}
{submitCaption}
submitInProgress={state === "submit"}
{focus}
{body}
{enableAttachments}
stylePadding="0.5rem 0.75rem"
on:close={() => {
if (onclose !== undefined) {
onclose();
} else {
state = "collapsed";
}
}}
on:submit={async ({ detail: { comment, embeds } }) => {
try {
state = "submit";
await submit(comment, Array.from(embeds.values()));
} finally {
state = "collapsed";
}
}} />
{:else}
<Border
hoverable
styleCursor="text"
variant="ghost"
styleHeight="40px"
styleWidth="100%"
onclick={() => {
state = "expanded";
if (onexpand !== undefined) {
onexpand();
}
}}>
<div style:width="100%" class="inactive">
{placeholder}
</div>
</Border>
{/if}
Loading

0 comments on commit 3303a33

Please sign in to comment.