Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/client/src/components/chat/MessageDropdown.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import type { Snippet } from "svelte";

const {
children,
x,
y,
visible,
}: { children: Snippet; x: number; y: number; visible: boolean } = $props();
</script>

{#if visible}
<div style={`top: ${y}px; left: ${x}px;}`} class="absolute z-10">
{@render children()}
</div>
{/if}
103 changes: 70 additions & 33 deletions packages/client/src/components/chat/MessageList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { Doc } from "@packages/convex/src/convex/_generated/dataModel";
import { useQuery } from "convex-svelte";
import { onMount } from "svelte";
import MessageDropdown from "./MessageDropdown.svelte";

interface Props {
channelId: Id<"channels">;
Expand Down Expand Up @@ -43,46 +44,82 @@
onMount(() => {
scrollToBottom();
});

let clientX = $state(0);
let clientY = $state(0);
let visibleDropdown = $state<Id<"messages"> | null>(null);
document.addEventListener("click", () => {
visibleDropdown = null;
});
</script>

<div bind:this={messagesContainer} class="flex-1 space-y-2 overflow-y-auto p-4">
{#if messages.data}
{#each messages.data as message (message._id)}
{#if message.parentId && messages.data.find((m) => m._id === message.parentId)}
<div class="flex items-center gap-2">
<span class="text-base-content/60 text-xs">返信</span>
<span class="text-primary font-semibold"
>{messagesById.get(message.parentId)?.author}</span
>
<span class="text-base-content/60 text-xs">
{messagesById.get(message.parentId)?.content}
</span>
</div>
{/if}
<div class="group relative flex flex-col">
<div class="flex items-baseline gap-2">
<span class="text-primary font-semibold">{message.author}</span>
<span class="text-base-content/60 text-xs">
{formatTime(message.createdAt)}
</span>
</div>
<div class="text-base-content ml-0 whitespace-pre-wrap">
{message.content}
</div>
<div
class="bg-base-100 absolute top-0 right-4 -translate-y-1/2 rounded-md border opacity-0 group-hover:opacity-100"
{#snippet dropdownContent()}
<ul
class="menu dropdown-content bg-base-100 absolute z-[1] w-40 rounded-md border p-2 shadow"
>
<div class="dropdown dropdown-end">
<button class="btn btn-ghost btn-sm p-2" tabindex="0"> ⋮ </button>
<ul
tabindex="0"
role="menu"
class="menu dropdown-content bg-base-100 z-[1] w-40 rounded-md border p-2 shadow"
<li>
<button onclick={() => (replyingTo = message)}>返信</button>
</li>
</ul>
{/snippet}
<MessageDropdown
x={clientX}
y={clientY}
visible={visibleDropdown === message._id}
>
{@render dropdownContent()}
</MessageDropdown>

<div
role="button"
tabindex="0"
class="p-1 hover:bg-sky-900"
oncontextmenu={(e) => {
e.preventDefault();
clientX = e.clientX;
clientY = e.clientY;
visibleDropdown = message._id;
}}
>
{#if message.parentId && messages.data.find((m) => m._id === message.parentId)}
<div class="flex items-center gap-2">
<span class="text-base-content/60 text-xs">返信</span>
<span class="text-primary font-semibold"
>{messagesById.get(message.parentId)?.author}</span
>
<li>
<button onclick={() => (replyingTo = message)}>返信</button>
</li>
</ul>
<span class="text-base-content/60 text-xs">
{messagesById.get(message.parentId)?.content}
</span>
</div>
{/if}
<div class="group relative flex flex-col">
<div class="flex items-baseline gap-2">
<span class="text-primary font-semibold">{message.author}</span>
<span class="text-base-content/60 text-xs">
{formatTime(message.createdAt)}
</span>
</div>
<div class="text-base-content ml-0 whitespace-pre-wrap">
{message.content}
</div>
<div
class="bg-base-100 absolute top-4 right-4 -translate-y-1/2 rounded-md border opacity-0 group-hover:opacity-100"
>
<div class="dropdown dropdown-end">
<button class="btn btn-ghost btn-sm p-2" tabindex="0"> ⋮ </button>
<ul
tabindex="0"
role="menu"
class="menu dropdown-content bg-base-100 z-[1] w-40 rounded-md border p-2 shadow"
>
<li>
<button onclick={() => (replyingTo = message)}>返信</button>
</li>
</ul>
</div>
</div>
</div>
</div>
Expand Down