From 9dd4778e449a48efb04d5eaa8c67e2edcc063525 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 4 Aug 2025 12:27:39 +0900 Subject: [PATCH 01/16] =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=83=91?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=83=88=E3=81=AE=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/EmojiPalette.svelte | 43 +++++++++++++++++++ .../src/components/chat/MessageInput.svelte | 16 +++++++ 2 files changed, 59 insertions(+) create mode 100644 packages/client/src/components/chat/EmojiPalette.svelte diff --git a/packages/client/src/components/chat/EmojiPalette.svelte b/packages/client/src/components/chat/EmojiPalette.svelte new file mode 100644 index 0000000..435254e --- /dev/null +++ b/packages/client/src/components/chat/EmojiPalette.svelte @@ -0,0 +1,43 @@ + + +
+
+

絵文字パレット (仮)

+ +
+
+ 😀 + 😂 + 😍 + 🤔 + 👍 +
+
diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index 9ca383c..24eef4f 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -2,6 +2,7 @@ import { api, type Id } from "@packages/convex"; import type { Doc } from "@packages/convex/src/convex/_generated/dataModel"; import { useConvexClient, useQuery } from "convex-svelte"; + import EmojiPalette from "./EmojiPalette.svelte"; interface Props { channelId: Id<"channels">; @@ -15,6 +16,8 @@ let messageContent = $state(""); let authorName = $state(""); + let showEmojiPalette = $state(false); + let emojiButtonRef = $state(null); $effect(() => { if (identity?.data && !authorName) { @@ -77,5 +80,18 @@ > 送信 + + {#if showEmojiPalette} + (showEmojiPalette = false)} + toggleButtonRef={emojiButtonRef} + /> + {/if} From 6dcd70648340c73dfefed1423fbb345aeddebcaf Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 4 Aug 2025 13:10:29 +0900 Subject: [PATCH 02/16] =?UTF-8?q?emoji-picker-element=E3=82=92=E5=B0=8E?= =?UTF-8?q?=E5=85=A5=E3=83=BB=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=81=AB=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/EmojiPalette.svelte | 33 ++++++++++--------- .../src/components/chat/MessageInput.svelte | 3 ++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/client/src/components/chat/EmojiPalette.svelte b/packages/client/src/components/chat/EmojiPalette.svelte index 435254e..af5b6e8 100644 --- a/packages/client/src/components/chat/EmojiPalette.svelte +++ b/packages/client/src/components/chat/EmojiPalette.svelte @@ -1,9 +1,11 @@ -
-
-

絵文字パレット (仮)

- -
-
- 😀 - 😂 - 😍 - 🤔 - 👍 -
+
+
diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index 24eef4f..a039b20 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -91,6 +91,9 @@ {#if showEmojiPalette} (showEmojiPalette = false)} + onEmojiSelected={(emoji) => { + messageContent += emoji; + }} toggleButtonRef={emojiButtonRef} /> {/if} From 64520cdda983a53006fbeb4ce11f917e9481fb71 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Sun, 17 Aug 2025 07:46:42 +0900 Subject: [PATCH 03/16] =?UTF-8?q?picker=E3=82=92JS=E3=81=A7=E5=88=B6?= =?UTF-8?q?=E5=BE=A1=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/src/components/chat/EmojiPalette.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/client/src/components/chat/EmojiPalette.svelte b/packages/client/src/components/chat/EmojiPalette.svelte index af5b6e8..1097a3a 100644 --- a/packages/client/src/components/chat/EmojiPalette.svelte +++ b/packages/client/src/components/chat/EmojiPalette.svelte @@ -1,5 +1,6 @@ -
- -
+
From 0d1ba77c6504f4c637325a817f655f8cf0bc895a Mon Sep 17 00:00:00 2001 From: nakomochi Date: Sun, 17 Aug 2025 09:44:14 +0900 Subject: [PATCH 04/16] =?UTF-8?q?reactions=E3=82=92=E8=BF=BD=E5=8A=A0=20me?= =?UTF-8?q?ssages=E3=81=ABuserId=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/MessageInput.svelte | 7 -- .../src/components/chat/MessageList.svelte | 3 + packages/convex/src/convex/messages.ts | 72 +++++++++++++++++++ packages/convex/src/convex/schema.ts | 9 +++ 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index f166d1e..1675be1 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -92,13 +92,6 @@ > 😀 -
{#if showEmojiPalette} +
  • + +
  • {/snippet} { + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error("User not authenticated"); + } + const perms = await getMessagePerms(ctx, { channelId: args.channelId, }); @@ -37,8 +43,74 @@ export const send = mutation({ channelId: args.channelId, content: args.content, author: args.author, + userId: userId, createdAt: Date.now(), parentId: args.parentId, }); }, }); + +export const getReactions = query({ + args: { messageId: v.id("messages") }, + handler: async (ctx, args) => { + return await ctx.db + .query("reactions") + .withIndex("by_message", (q) => q.eq("messageId", args.messageId)) + .collect(); + }, +}); + +export const addReaction = mutation({ + args: { + messageId: v.id("messages"), + emoji: v.string(), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error("User not authenticated"); + } + + const existingReaction = await ctx.db + .query("reactions") + .withIndex("by_message", (q) => q.eq("messageId", args.messageId)) + .filter((q) => q.eq(q.field("userId"), userId)) + .filter((q) => q.eq(q.field("emoji"), args.emoji)) + .first(); + + if (existingReaction) { + return; + } + + await ctx.db.insert("reactions", { + messageId: args.messageId, + userId: userId, + emoji: args.emoji, + createdAt: Date.now(), + }); + }, +}); + +export const removeReaction = mutation({ + args: { + messageId: v.id("messages"), + emoji: v.string(), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error("User not authenticated"); + } + + const reaction = await ctx.db + .query("reactions") + .withIndex("by_message", (q) => q.eq("messageId", args.messageId)) + .filter((q) => q.eq(q.field("userId"), userId)) + .filter((q) => q.eq(q.field("emoji"), args.emoji)) + .first(); + + if (reaction) { + await ctx.db.delete(reaction._id); + } + }, +}); diff --git a/packages/convex/src/convex/schema.ts b/packages/convex/src/convex/schema.ts index e55f27c..a43c99b 100644 --- a/packages/convex/src/convex/schema.ts +++ b/packages/convex/src/convex/schema.ts @@ -37,8 +37,17 @@ export default defineSchema({ channelId: v.id("channels"), content: v.string(), author: v.string(), + userId: v.id("users"), createdAt: v.number(), parentId: v.optional(v.id("messages")), }).index("by_channel", ["channelId"]), + reactions: defineTable({ + messageId: v.id("messages"), + userId: v.id("users"), + emoji: v.string(), + createdAt: v.number(), + }) + .index("by_message", ["messageId"]) + .index("by_user", ["userId"]), ...authTables, }); From 54a4f458d287e61b76ad484123fb4c87a9336c93 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Thu, 4 Sep 2025 21:39:15 +0900 Subject: [PATCH 05/16] =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/MessageList.svelte | 2 + .../src/components/chat/ReactionList.svelte | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 packages/client/src/components/chat/ReactionList.svelte diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index e04b4ca..1f11672 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -4,6 +4,7 @@ import { useQuery } from "convex-svelte"; import { onMount } from "svelte"; import MessageDropdown from "./MessageDropdown.svelte"; + import ReactionList from "./ReactionList.svelte"; interface Props { channelId: Id<"channels">; @@ -108,6 +109,7 @@
    {message.content}
    +
    diff --git a/packages/client/src/components/chat/ReactionList.svelte b/packages/client/src/components/chat/ReactionList.svelte new file mode 100644 index 0000000..156b27e --- /dev/null +++ b/packages/client/src/components/chat/ReactionList.svelte @@ -0,0 +1,64 @@ + + +
    + {#if me.data} + {#each [...reactionsByEmoji.entries()] as [emoji, detail]} + {@const amIin = detail.me} + {@const count = detail.count} +
    + +
    + {/each} + {/if} +
    From 794ed41b862fe63dec932af1cd1a0ce8c71e9e27 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 8 Sep 2025 12:22:08 +0900 Subject: [PATCH 06/16] =?UTF-8?q?=E3=83=91=E3=83=AC=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/EmojiPalette.svelte | 31 +++++++++++----- .../src/components/chat/MessageList.svelte | 37 ++++++++++++++++++- packages/convex/src/convex/messages.ts | 2 + 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/packages/client/src/components/chat/EmojiPalette.svelte b/packages/client/src/components/chat/EmojiPalette.svelte index 1097a3a..93d656b 100644 --- a/packages/client/src/components/chat/EmojiPalette.svelte +++ b/packages/client/src/components/chat/EmojiPalette.svelte @@ -4,9 +4,11 @@ interface Props { onClose: () => void; onEmojiSelected: (emoji: string) => void; - toggleButtonRef: HTMLElement | null; + toggleButtonRef?: HTMLElement; + x?: number; + y?: number; } - let { onClose, onEmojiSelected, toggleButtonRef }: Props = $props(); + let { onClose, onEmojiSelected, toggleButtonRef, x, y }: Props = $props(); let paletteRef: HTMLElement; $effect(() => { @@ -23,16 +25,19 @@ const picker = new Picker(); paletteRef.appendChild(picker); + paletteRef.addEventListener("mousedown", (event: MouseEvent) => { + event.stopPropagation(); + }); + document.addEventListener("mousedown", handleClickOutside); const emojiPicker = document.querySelector("emoji-picker"); - if (emojiPicker) { - emojiPicker.addEventListener("emoji-click", (event) => { - const emoji = event.detail.unicode; - if (!emoji) return; - onEmojiSelected(emoji); - }); - } + + emojiPicker?.addEventListener("emoji-click", (event) => { + const emoji = event.detail.unicode; + if (!emoji) return; + onEmojiSelected(emoji); + }); return () => { document.removeEventListener("mousedown", handleClickOutside); @@ -43,4 +48,10 @@ }); -
    +
    diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index 1f11672..27c94ad 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -3,6 +3,8 @@ import type { Doc } from "@packages/convex/src/convex/_generated/dataModel"; import { useQuery } from "convex-svelte"; import { onMount } from "svelte"; + import { useMutation } from "~/lib/useMutation.svelte"; + import EmojiPalette from "./EmojiPalette.svelte"; import MessageDropdown from "./MessageDropdown.svelte"; import ReactionList from "./ReactionList.svelte"; @@ -17,6 +19,8 @@ channelId, })); + const addReaction = useMutation(api.messages.addReaction); + const messagesById = $derived( new Map(messages.data?.map((message) => [message._id, message])), ); @@ -49,6 +53,7 @@ let clientX = $state(0); let clientY = $state(0); let visibleDropdown = $state | null>(null); + let paletteVisibleFor = $state | null>(null); document.addEventListener("click", () => { visibleDropdown = null; }); @@ -65,7 +70,9 @@
  • - +
  • {/snippet} @@ -77,6 +84,29 @@ {@render dropdownContent()} + {#if paletteVisibleFor && paletteVisibleFor === message._id} + { + console.log("closed"); + paletteVisibleFor = null; + }} + onEmojiSelected={async (emoji) => { + console.log("Emoji selected:", emoji); + if (!paletteVisibleFor) return; + console.log( + "Selected emoji", + emoji, + "for message", + paletteVisibleFor, + ); + await addReaction.run({ messageId: paletteVisibleFor, emoji }); + paletteVisibleFor = null; + }} + /> + {/if} +
    +
  • + +
  • diff --git a/packages/convex/src/convex/messages.ts b/packages/convex/src/convex/messages.ts index 589b341..a1ccd31 100644 --- a/packages/convex/src/convex/messages.ts +++ b/packages/convex/src/convex/messages.ts @@ -71,6 +71,8 @@ export const addReaction = mutation({ throw new Error("User not authenticated"); } + console.log("Adding reaction", args.emoji, "to message", args.messageId); + const existingReaction = await ctx.db .query("reactions") .withIndex("by_message", (q) => q.eq("messageId", args.messageId)) From 85b463c483ac04a4615c6623bc6279de19494f2c Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 8 Sep 2025 12:27:56 +0900 Subject: [PATCH 07/16] fix --- packages/client/src/components/chat/ReactionList.svelte | 2 +- .../src/components/organization/OrganizationSelector.svelte | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/client/src/components/chat/ReactionList.svelte b/packages/client/src/components/chat/ReactionList.svelte index 156b27e..d3ff055 100644 --- a/packages/client/src/components/chat/ReactionList.svelte +++ b/packages/client/src/components/chat/ReactionList.svelte @@ -51,7 +51,7 @@ {@const count = detail.count}
    From f5687fddfcdd06c050a7330b6fd4b431edbd67d0 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 8 Sep 2025 12:53:09 +0900 Subject: [PATCH 08/16] =?UTF-8?q?dropdown=E3=81=AE=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/MessageList.svelte | 40 +++++++------------ .../client/src/icons/mdi-dots-vertical.svelte | 5 +++ 2 files changed, 20 insertions(+), 25 deletions(-) create mode 100644 packages/client/src/icons/mdi-dots-vertical.svelte diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index 27c94ad..930d2e6 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -3,6 +3,7 @@ import type { Doc } from "@packages/convex/src/convex/_generated/dataModel"; import { useQuery } from "convex-svelte"; import { onMount } from "svelte"; + import MdiDotsVertical from "~/icons/mdi-dots-vertical.svelte"; import { useMutation } from "~/lib/useMutation.svelte"; import EmojiPalette from "./EmojiPalette.svelte"; import MessageDropdown from "./MessageDropdown.svelte"; @@ -74,6 +75,9 @@ >リアクションを付ける +
  • + +
  • {/snippet} { - console.log("closed"); paletteVisibleFor = null; }} onEmojiSelected={async (emoji) => { - console.log("Emoji selected:", emoji); if (!paletteVisibleFor) return; - console.log( - "Selected emoji", - emoji, - "for message", - paletteVisibleFor, - ); await addReaction.run({ messageId: paletteVisibleFor, emoji }); paletteVisibleFor = null; }} @@ -143,23 +139,17 @@
    - +
    diff --git a/packages/client/src/icons/mdi-dots-vertical.svelte b/packages/client/src/icons/mdi-dots-vertical.svelte new file mode 100644 index 0000000..9855bb1 --- /dev/null +++ b/packages/client/src/icons/mdi-dots-vertical.svelte @@ -0,0 +1,5 @@ + + + From c0e2f6679dae367353e39ca63d1e839e33ee759e Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 8 Sep 2025 15:47:59 +0900 Subject: [PATCH 09/16] =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=92=E3=81=97=E3=81=9F=E4=BA=BA=E3=81=AE?= =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/MessageList.svelte | 33 ++++-- .../components/chat/ReactionButtons.svelte | 62 ++++++++++ .../src/components/chat/ReactionList.svelte | 111 ++++++++++++------ packages/client/src/routes/+layout.svelte | 7 ++ packages/convex/src/convex/users.ts | 20 ++++ 5 files changed, 185 insertions(+), 48 deletions(-) create mode 100644 packages/client/src/components/chat/ReactionButtons.svelte diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index 930d2e6..fd40b25 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -2,11 +2,13 @@ import { api, type Id } from "@packages/convex"; import type { Doc } from "@packages/convex/src/convex/_generated/dataModel"; import { useQuery } from "convex-svelte"; - import { onMount } from "svelte"; + import { getContext, onMount } from "svelte"; + import { ModalManager } from "$lib/modal/modal.svelte"; import MdiDotsVertical from "~/icons/mdi-dots-vertical.svelte"; import { useMutation } from "~/lib/useMutation.svelte"; import EmojiPalette from "./EmojiPalette.svelte"; import MessageDropdown from "./MessageDropdown.svelte"; + import ReactionButtons from "./ReactionButtons.svelte"; import ReactionList from "./ReactionList.svelte"; interface Props { @@ -54,7 +56,8 @@ let clientX = $state(0); let clientY = $state(0); let visibleDropdown = $state | null>(null); - let paletteVisibleFor = $state | null>(null); + let reactionPaletteVisibleFor = $state | null>(null); + const reactionListManager = getContext("modal-manager"); document.addEventListener("click", () => { visibleDropdown = null; }); @@ -63,6 +66,10 @@
    {#if messages.data} {#each messages.data as message (message._id)} + {#snippet reactionListSnippet()} + + {/snippet} + {#snippet dropdownContent()} {/snippet} @@ -88,17 +98,20 @@ {@render dropdownContent()} - {#if paletteVisibleFor && paletteVisibleFor === message._id} + {#if reactionPaletteVisibleFor && reactionPaletteVisibleFor === message._id} { - paletteVisibleFor = null; + reactionPaletteVisibleFor = null; }} onEmojiSelected={async (emoji) => { - if (!paletteVisibleFor) return; - await addReaction.run({ messageId: paletteVisibleFor, emoji }); - paletteVisibleFor = null; + if (!reactionPaletteVisibleFor) return; + await addReaction.run({ + messageId: reactionPaletteVisibleFor, + emoji, + }); + reactionPaletteVisibleFor = null; }} /> {/if} @@ -135,7 +148,7 @@
    {message.content}
    - +
    diff --git a/packages/client/src/components/chat/ReactionButtons.svelte b/packages/client/src/components/chat/ReactionButtons.svelte new file mode 100644 index 0000000..2eee92d --- /dev/null +++ b/packages/client/src/components/chat/ReactionButtons.svelte @@ -0,0 +1,62 @@ + + +
    + {#each [...reactionsByEmoji.entries()] as [emoji, detail]} + {@const amIin = detail.me} + {@const count = detail.count} +
    + +
    + {/each} +
    diff --git a/packages/client/src/components/chat/ReactionList.svelte b/packages/client/src/components/chat/ReactionList.svelte index d3ff055..0365793 100644 --- a/packages/client/src/components/chat/ReactionList.svelte +++ b/packages/client/src/components/chat/ReactionList.svelte @@ -1,8 +1,6 @@ -
    - {#if me.data} - {#each [...reactionsByEmoji.entries()] as [emoji, detail]} - {@const amIin = detail.me} - {@const count = detail.count} -
    - +
    + {#if reactionDetailsByEmoji.size > 0} +
    +
    + {#each [...reactionDetailsByEmoji.entries()] as [emoji, detail]} + + {/each} +
    +
    + {#if selectedEmoji} + + + {#if userNamesById.data && reactionDetailsByEmoji.get(selectedEmoji)} + {@const userIdsForSelectedEmoji = + reactionDetailsByEmoji.get(selectedEmoji)?.users ?? []} + {#if userIdsForSelectedEmoji.length === 0} + + + + {/if} + {#each userIdsForSelectedEmoji as userId} + + + + {/each} + {:else if userNamesById.isLoading || reactions.isLoading} + + + + {:else} + + + + {/if} + +
    No one has reacted with this emoji.
    {userNamesById.data[userId]}
    Loading...
    Error
    + {/if}
    - {/each} +
    + {:else} +
    +

    There are no reactions yet.

    +
    {/if}
    diff --git a/packages/client/src/routes/+layout.svelte b/packages/client/src/routes/+layout.svelte index 4d83f8a..f844478 100644 --- a/packages/client/src/routes/+layout.svelte +++ b/packages/client/src/routes/+layout.svelte @@ -3,7 +3,9 @@ import { setupConvexAuth } from "@mmailaender/convex-auth-svelte/sveltekit"; import { setupConvex } from "convex-svelte"; + import { setContext } from "svelte"; import { PUBLIC_CONVEX_URL } from "$lib/env.ts"; + import Modal, { ModalManager } from "$lib/modal/modal.svelte"; const { children, data } = $props(); @@ -13,6 +15,11 @@ getServerState: () => data.authState, convexUrl: PUBLIC_CONVEX_URL, }); + + const modalManager = new ModalManager(); + setContext("modal-manager", modalManager); + + {@render children()} diff --git a/packages/convex/src/convex/users.ts b/packages/convex/src/convex/users.ts index b273178..2d795cb 100644 --- a/packages/convex/src/convex/users.ts +++ b/packages/convex/src/convex/users.ts @@ -1,4 +1,6 @@ import { getAuthUserId } from "@convex-dev/auth/server"; +import { v } from "convex/values"; +import type { Id } from "./_generated/dataModel"; import { query } from "./_generated/server"; export const me = query({ @@ -11,3 +13,21 @@ export const me = query({ return await ctx.db.get(userId); }, }); + +export const getUserNames = query({ + args: { + userIds: v.array(v.id("users")), + }, + handler: async (ctx, { userIds }) => { + const users = await Promise.all( + userIds.map((userId) => ctx.db.get(userId)), + ); + const userNames: Record, string> = Object.fromEntries( + users + .filter((user) => user !== null) + .map((user) => [user._id, user.name ?? ""]), + ); + + return userNames; + }, +}); From 9cae4d62fcb4eeccbd7bf08bcc4ccd30455f59c5 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 8 Sep 2025 16:13:55 +0900 Subject: [PATCH 10/16] fix --- .../client/src/components/chat/MessageList.svelte | 11 ++++++----- .../client/src/components/chat/ReactionList.svelte | 6 +++--- packages/client/src/lib/modal/modal.svelte | 1 - packages/client/src/routes/+layout.svelte | 7 ------- packages/convex/src/convex/messages.ts | 2 -- 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index fd40b25..0df84d6 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -2,8 +2,8 @@ import { api, type Id } from "@packages/convex"; import type { Doc } from "@packages/convex/src/convex/_generated/dataModel"; import { useQuery } from "convex-svelte"; - import { getContext, onMount } from "svelte"; - import { ModalManager } from "$lib/modal/modal.svelte"; + import { onMount } from "svelte"; + import Modal, { ModalManager } from "$lib/modal/modal.svelte"; import MdiDotsVertical from "~/icons/mdi-dots-vertical.svelte"; import { useMutation } from "~/lib/useMutation.svelte"; import EmojiPalette from "./EmojiPalette.svelte"; @@ -57,12 +57,14 @@ let clientY = $state(0); let visibleDropdown = $state | null>(null); let reactionPaletteVisibleFor = $state | null>(null); - const reactionListManager = getContext("modal-manager"); + const modalManager = new ModalManager(); document.addEventListener("click", () => { visibleDropdown = null; }); + +
    {#if messages.data} {#each messages.data as message (message._id)} @@ -83,8 +85,7 @@ >
  • -
  • diff --git a/packages/client/src/components/chat/ReactionList.svelte b/packages/client/src/components/chat/ReactionList.svelte index 0365793..01db719 100644 --- a/packages/client/src/components/chat/ReactionList.svelte +++ b/packages/client/src/components/chat/ReactionList.svelte @@ -47,12 +47,12 @@
    {#if reactionDetailsByEmoji.size > 0} -
    -
    +
    +
    {#each [...reactionDetailsByEmoji.entries()] as [emoji, detail]} @@ -99,7 +100,6 @@ onEmojiSelected={(emoji) => { messageContent += emoji; }} - toggleButtonRef={emojiButtonRef} /> {/if}
    diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index e782cbc..59f873c 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -59,53 +59,6 @@ let reactionPaletteVisibleFor = $state | null>(null); const modalManager = new ModalManager(); - let dropdownElement: HTMLUListElement | undefined = $state(); - type TriggerDetails = { - type: "contextmenu" | "click"; - clientX: number; - clientY: number; - rect?: DOMRect; - }; - let triggerDetails: TriggerDetails | undefined = $state(); - - $effect(() => { - if (visibleDropdown && dropdownElement && triggerDetails) { - const menuWidth = dropdownElement.offsetWidth; - const menuHeight = dropdownElement.offsetHeight; - - let newX: number; - let newY: number; - - if (triggerDetails.type === "click" && triggerDetails.rect) { - const rect = triggerDetails.rect; - newX = rect.left; - if (rect.left + menuWidth > window.innerWidth) { - newX = rect.right - menuWidth; - } - newY = rect.bottom; - if (rect.bottom + menuHeight > window.innerHeight) { - newY = rect.top - menuHeight; - } - } else { - // contextmenu - const { clientX: cx, clientY: cy } = triggerDetails; - newX = cx; - if (cx + menuWidth > window.innerWidth) { - newX = cx - menuWidth; - } - newY = cy; - if (cy + menuHeight > window.innerHeight) { - newY = cy - menuHeight; - } - } - - clientX = newX; - clientY = newY; - - triggerDetails = undefined; - } - }); - document.addEventListener("click", () => { visibleDropdown = null; }); @@ -128,8 +81,12 @@
  • - { + e.stopPropagation(); + reactionPaletteVisibleFor = message._id; + visibleDropdown = null; + }}>リアクションを付ける
  • From dea7ff2e9758681bf206426c62ce9de0cfd25d6b Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 8 Sep 2025 17:01:50 +0900 Subject: [PATCH 13/16] =?UTF-8?q?=E8=BF=94=E4=BF=A1=E3=81=AE=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/chat/MessageInput.svelte | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index 1160c26..27231d6 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -2,6 +2,7 @@ import { api, type Id } from "@packages/convex"; import type { Doc } from "@packages/convex/src/convex/_generated/dataModel"; import { useQuery } from "convex-svelte"; + import MdiClose from "~/icons/mdi-close.svelte"; import { useMutation } from "~/lib/useMutation.svelte.ts"; import EmojiPalette from "./EmojiPalette.svelte"; @@ -49,10 +50,20 @@
    {#if replyingTo} -
    - 返信先: - {replyingTo.author} - {replyingTo.content} +
    +
    + 返信先: + {replyingTo.author} + : {replyingTo.content} +
    +
    {/if} From 2a3a37bb1f350ddc3a4a0ff6efa3b2d2fac28574 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Thu, 11 Sep 2025 10:01:06 +0900 Subject: [PATCH 14/16] =?UTF-8?q?ReactionButtons=E3=81=A8FileAttachment?= =?UTF-8?q?=E3=81=AE=E9=A0=86=E5=BA=8F=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/chat/MessageList.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index fe64e33..0f966a7 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -159,7 +159,6 @@
    {message.content}
    - {#if message.attachments && message.attachments.length > 0} @@ -188,6 +187,7 @@
    +
  • {:else} From c0665c8ae119dfbacbcc27e4a46d99a81cd33898 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Thu, 11 Sep 2025 10:09:59 +0900 Subject: [PATCH 15/16] fix --- packages/client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/package.json b/packages/client/package.json index 4e8ebf4..4d72a9f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -51,6 +51,7 @@ "@tauri-apps/plugin-opener": "^2.4.0", "convex": "^1.25.4", "convex-svelte": "^0.0.11", + "emoji-picker-element": "^1.27.0", "robot3": "^1.1.1", "runed": "^0.31.0", "unplugin-icons": "^22.2.0" From 950af282efdd4b485b6b15332a344fcd1f927506 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Thu, 11 Sep 2025 10:11:58 +0900 Subject: [PATCH 16/16] =?UTF-8?q?=E4=BE=9D=E5=AD=98=E9=96=A2=E4=BF=82?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bun.lock | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bun.lock b/bun.lock index 365738b..3050f1a 100644 --- a/bun.lock +++ b/bun.lock @@ -30,6 +30,7 @@ "@tauri-apps/plugin-opener": "^2.4.0", "convex": "^1.25.4", "convex-svelte": "^0.0.11", + "emoji-picker-element": "^1.27.0", "robot3": "^1.1.1", "runed": "^0.31.0", "unplugin-icons": "^22.2.0", @@ -676,6 +677,8 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "emoji-picker-element": ["emoji-picker-element@1.27.0", "", {}, "sha512-CeN9g5/kq41+BfYPDpAbE2ejZRHbs1faFDmU9+E9wGA4JWLkok9zo1hwcAFnUhV4lPR3ZuLHiJxNG1mpjoF4TQ=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="],