From a71aa4347d1d7ff8796c4c369443dba57920c6d9 Mon Sep 17 00:00:00 2001 From: Kanata844 Date: Sat, 20 Sep 2025 17:34:06 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E6=8A=95=E7=A5=A8=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=81=AE=E5=9F=BA=E6=9C=AC=E3=82=92=E4=BD=9C=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=80=81=E3=81=82=E3=81=A8=E8=87=AA=E5=88=86=E3=81=8C=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=81=97=E3=81=9F=E3=83=8B=E3=83=83=E3=82=AF=E3=83=8D?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=81=8C=E5=8F=8D=E6=98=A0=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/channels/Channel.svelte | 2 +- .../src/components/chat/MessageInput.svelte | 64 ++++++++++- .../src/components/chat/MessageList.svelte | 9 +- .../src/components/chat/ReactionList.svelte | 10 +- .../src/components/chat/VoteMaker.svelte | 71 ++++++++++++ .../src/components/chat/VoteViewer.svelte | 107 ++++++++++++++++++ packages/convex/package.json | 2 +- packages/convex/src/convex/messages.ts | 2 + packages/convex/src/convex/schema.ts | 14 +++ packages/convex/src/convex/users.ts | 33 ++++++ packages/convex/src/convex/vote.ts | 50 ++++++++ 11 files changed, 356 insertions(+), 8 deletions(-) create mode 100644 packages/client/src/components/chat/VoteMaker.svelte create mode 100644 packages/client/src/components/chat/VoteViewer.svelte create mode 100644 packages/convex/src/convex/vote.ts diff --git a/packages/client/src/components/channels/Channel.svelte b/packages/client/src/components/channels/Channel.svelte index 9a13fc8..9bb37ad 100644 --- a/packages/client/src/components/channels/Channel.svelte +++ b/packages/client/src/components/channels/Channel.svelte @@ -30,5 +30,5 @@ {/if} - + diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index 46a0041..e32e858 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -1,13 +1,14 @@
@@ -116,6 +164,10 @@ /> {/if} + {#if showVoteMaker} + + {/if} +
{showFileSelector ? "キャンセル" : "ファイル添付"} +
diff --git a/packages/client/src/components/chat/MessageList.svelte b/packages/client/src/components/chat/MessageList.svelte index 0f966a7..7811e9d 100644 --- a/packages/client/src/components/chat/MessageList.svelte +++ b/packages/client/src/components/chat/MessageList.svelte @@ -11,13 +11,15 @@ import MessageDropdown from "./MessageDropdown.svelte"; import ReactionButtons from "./ReactionButtons.svelte"; import ReactionList from "./ReactionList.svelte"; + import VoteViewer from "./VoteViewer.svelte"; interface Props { + organizationId: Id<"organizations">; channelId: Id<"channels">; replyingTo: Doc<"messages"> | null; } - let { channelId, replyingTo = $bindable() }: Props = $props(); + let { organizationId, channelId, replyingTo = $bindable() }: Props = $props(); const messages = useQuery(api.messages.list, () => ({ channelId, @@ -71,7 +73,7 @@ {#if messages.data} {#each messages.data as message (message._id)} {#snippet reactionListSnippet()} - + {/snippet} {#snippet dropdownContent()} @@ -168,6 +170,9 @@ {/each} {/if} + {#if message.vote} + + {/if}
diff --git a/packages/client/src/components/chat/ReactionList.svelte b/packages/client/src/components/chat/ReactionList.svelte index 01db719..71dd0e8 100644 --- a/packages/client/src/components/chat/ReactionList.svelte +++ b/packages/client/src/components/chat/ReactionList.svelte @@ -3,10 +3,11 @@ import { useQuery } from "convex-svelte"; interface Props { + organizationId: Id<"organizations">; messageId: Id<"messages">; } - let { messageId }: Props = $props(); + let { organizationId, messageId }: Props = $props(); const reactions = useQuery(api.messages.getReactions, () => ({ messageId })); @@ -30,8 +31,13 @@ reactions.data ? [...new Set(reactions.data.map((r) => r.userId))] : [], ); - const userNamesById = useQuery(api.users.getUserNames, () => ({ + // const userNamesById = useQuery(api.users.getUserNames, () => ({ + // userIds: allUserIdsInReactions, + // })); + + const userNamesById = useQuery(api.users.getUserNicknames, () => ({ userIds: allUserIdsInReactions, + organizationId: organizationId, })); function toggleUserList(emoji: string) { diff --git a/packages/client/src/components/chat/VoteMaker.svelte b/packages/client/src/components/chat/VoteMaker.svelte new file mode 100644 index 0000000..5dceba8 --- /dev/null +++ b/packages/client/src/components/chat/VoteMaker.svelte @@ -0,0 +1,71 @@ + + +
+

投票のタイトル:

+ +
+ +
+

一人が投票できる最大数:

+ { + vote.maxVotes = vote.maxVotes ?? 0; + }} + /> +
+
+ {#each vote.voteOptions as option, i} +
+

{i}:{option}

+ +
+ {/each} +
+ +
+

選択肢を追加:

+ + +
diff --git a/packages/client/src/components/chat/VoteViewer.svelte b/packages/client/src/components/chat/VoteViewer.svelte new file mode 100644 index 0000000..16a2397 --- /dev/null +++ b/packages/client/src/components/chat/VoteViewer.svelte @@ -0,0 +1,107 @@ + + +
+

投票:

+

{vote.data?.title}

+

+ 一人の最大投票数:{vote.data?.maxVotes}票 +

+ {#each vote.data?.voteOptions as option, i} +
+

+ {option}{isResultVisible + ? ":" + numbersOfVotersPerOption[i] + "人" + : ""} +

+ +
+ {/each} + +
diff --git a/packages/convex/package.json b/packages/convex/package.json index 98c7b83..6b163d0 100644 --- a/packages/convex/package.json +++ b/packages/convex/package.json @@ -16,7 +16,7 @@ "dependencies": { "@auth/core": "^0.40.0", "@convex-dev/auth": "^0.0.87", - "convex": "^1.25.2", + "convex": "^1.27.1", "resend": "^4.7.0" } } diff --git a/packages/convex/src/convex/messages.ts b/packages/convex/src/convex/messages.ts index e8311f6..ac5365a 100644 --- a/packages/convex/src/convex/messages.ts +++ b/packages/convex/src/convex/messages.ts @@ -27,6 +27,7 @@ export const send = mutation({ author: v.string(), parentId: v.optional(v.id("messages")), attachments: v.optional(v.array(v.id("files"))), + vote: v.optional(v.id("votes")), }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); @@ -54,6 +55,7 @@ export const send = mutation({ createdAt: Date.now(), parentId: args.parentId, attachments: args.attachments, + vote: args.vote, }); }, }); diff --git a/packages/convex/src/convex/schema.ts b/packages/convex/src/convex/schema.ts index 044203e..b0f147e 100644 --- a/packages/convex/src/convex/schema.ts +++ b/packages/convex/src/convex/schema.ts @@ -42,6 +42,8 @@ export default defineSchema({ parentId: v.optional(v.id("messages")), // 添付ファイル attachments: v.optional(v.array(v.id("files"))), + //投票 + vote: v.optional(v.id("votes")), }).index("by_channel", ["channelId"]), reactions: defineTable({ messageId: v.id("messages"), @@ -51,6 +53,18 @@ export default defineSchema({ }) .index("by_message", ["messageId"]) .index("by_user", ["userId"]), + votes: defineTable({ + title: v.string(), + maxVotes: v.number(), + //numberOfOptions: v.number(), + voteOptions: v.array(v.string()), + voters: v.array( + v.object({ + userId: v.id("users"), + votedOptions: v.array(v.number()), + }), + ), + }), personalization: defineTable({ userId: v.id("users"), organizationId: v.id("organizations"), diff --git a/packages/convex/src/convex/users.ts b/packages/convex/src/convex/users.ts index 2d795cb..757267f 100644 --- a/packages/convex/src/convex/users.ts +++ b/packages/convex/src/convex/users.ts @@ -31,3 +31,36 @@ export const getUserNames = query({ return userNames; }, }); + +export const getUserNicknames = query({ + args: { + userIds: v.array(v.id("users")), + organizationId: v.id("organizations"), + }, + handler: async (ctx, { userIds, organizationId }) => { + const users = await Promise.all( + userIds.map((userId) => ctx.db.get(userId)), + ); + const personalizations = await Promise.all( + userIds.map((userId) => + ctx.db + .query("personalization") + .filter((q) => q.eq(q.field("userId"), userId)) + .filter((q) => q.eq(q.field("organizationId"), organizationId)) + .unique(), + ), + ); + const userNicknames: Record, string> = Object.fromEntries( + users + .filter((user) => user !== null) + .map((user) => [ + user._id, + personalizations.find((p) => p?.userId === user._id)?.nickname ?? + user.name ?? + "", + ]), + ); + + return userNicknames; + }, +}); diff --git a/packages/convex/src/convex/vote.ts b/packages/convex/src/convex/vote.ts new file mode 100644 index 0000000..2e6ee2a --- /dev/null +++ b/packages/convex/src/convex/vote.ts @@ -0,0 +1,50 @@ +import { v } from "convex/values"; +import { mutation, query } from "./_generated/server"; +import type { Id } from "./_generated/dataModel"; + +export const addVote = mutation({ + args: { + title: v.string(), + maxVotes: v.number(), + voteOptions: v.array(v.string()), + }, + handler: async (ctx, args) => { + const id = await ctx.db.insert("votes", { + title: args.title, + maxVotes: args.maxVotes, + voteOptions: args.voteOptions, + voters: [], + }); + return id; + }, +}); + +export const getVote = query({ + args: { + id: v.id("votes"), + }, + handler: async (ctx, args) => { + return await ctx.db.get(args.id); + }, +}); + +export const vote = mutation({ + args: { + voteId: v.id("votes"), + userId: v.id("users"), + votedOptions: v.array(v.number()), + }, + handler: async (ctx, args) => { + const vote = await ctx.db.get(args.voteId); + const tempVoters = vote?.voters.filter( + (v: { userId: Id<"users">; votedOptions: Array }) => + v.userId !== args.userId, + ); + await ctx.db.patch(args.voteId, { + voters: [ + ...tempVoters, + { userId: args.userId, votedOptions: args.votedOptions }, + ], + }); + }, +}); From 45e4776dc851f11573367090a4605d612e2321ad Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:54:02 +0900 Subject: [PATCH 2/4] run autofix format and lint --- packages/client/src/components/chat/MessageInput.svelte | 2 +- packages/client/src/components/chat/VoteViewer.svelte | 6 +++--- packages/convex/src/convex/vote.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index e32e858..5f07c3f 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -71,7 +71,7 @@ (it) => it.id, ); - let voteId = undefined; + let voteId; if (vote.title.trim() && vote.voteOptions.length !== 0) { voteId = await convex.mutation(api.vote.addVote, { title: vote.title, diff --git a/packages/client/src/components/chat/VoteViewer.svelte b/packages/client/src/components/chat/VoteViewer.svelte index 16a2397..006b04d 100644 --- a/packages/client/src/components/chat/VoteViewer.svelte +++ b/packages/client/src/components/chat/VoteViewer.svelte @@ -11,12 +11,12 @@ let isResultVisible = $state(false); - let numbersOfVotersPerOption = $state>(Array()); + let numbersOfVotersPerOption = $state>([] as number[]); let selectedOptions = $state>([]); if (vote.data) { - let tempNumbersOfVotersPerOption = Array(); + let tempNumbersOfVotersPerOption: number[] = []; for (let i = 0; i < vote.data.voteOptions.length; i++) { let num = 0; for (let j = 0; j < vote.data.voters.length; j++) { @@ -40,7 +40,7 @@ $effect(() => { if (vote.data) { - let tempNumbersOfVotersPerOption = Array(); + let tempNumbersOfVotersPerOption: number[] = []; for (let i = 0; i < vote.data.voteOptions.length; i++) { let num = 0; for (let j = 0; j < vote.data.voters.length; j++) { diff --git a/packages/convex/src/convex/vote.ts b/packages/convex/src/convex/vote.ts index 2e6ee2a..f337a7c 100644 --- a/packages/convex/src/convex/vote.ts +++ b/packages/convex/src/convex/vote.ts @@ -1,6 +1,6 @@ import { v } from "convex/values"; -import { mutation, query } from "./_generated/server"; import type { Id } from "./_generated/dataModel"; +import { mutation, query } from "./_generated/server"; export const addVote = mutation({ args: { From 9c0c4c416d61473c65eccea86dda98ef5e2043c3 Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:56:22 +0900 Subject: [PATCH 3/4] format --- packages/client/src/components/chat/MessageInput.svelte | 2 +- packages/client/src/components/chat/VoteViewer.svelte | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/chat/MessageInput.svelte b/packages/client/src/components/chat/MessageInput.svelte index 5f07c3f..d3736b7 100644 --- a/packages/client/src/components/chat/MessageInput.svelte +++ b/packages/client/src/components/chat/MessageInput.svelte @@ -71,7 +71,7 @@ (it) => it.id, ); - let voteId; + let voteId: Id<"votes"> | undefined; if (vote.title.trim() && vote.voteOptions.length !== 0) { voteId = await convex.mutation(api.vote.addVote, { title: vote.title, diff --git a/packages/client/src/components/chat/VoteViewer.svelte b/packages/client/src/components/chat/VoteViewer.svelte index 006b04d..d5fe7b5 100644 --- a/packages/client/src/components/chat/VoteViewer.svelte +++ b/packages/client/src/components/chat/VoteViewer.svelte @@ -11,9 +11,9 @@ let isResultVisible = $state(false); - let numbersOfVotersPerOption = $state>([] as number[]); + let numbersOfVotersPerOption: number[] = $state([]); - let selectedOptions = $state>([]); + let selectedOptions: number[] = $state([]); if (vote.data) { let tempNumbersOfVotersPerOption: number[] = []; From 271260480840643ac421ec51fc9695e2f71f9e9a Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:59:45 +0900 Subject: [PATCH 4/4] update lockfile --- bun.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 3050f1a..57d87bd 100644 --- a/bun.lock +++ b/bun.lock @@ -62,7 +62,7 @@ "dependencies": { "@auth/core": "^0.40.0", "@convex-dev/auth": "^0.0.87", - "convex": "^1.25.2", + "convex": "^1.27.1", "resend": "^4.7.0", }, "devDependencies": { @@ -1101,6 +1101,8 @@ "@mmailaender/convex-auth-svelte/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], + "@packages/convex/convex": ["convex@1.27.1", "", { "dependencies": { "esbuild": "0.25.4", "jwt-decode": "^4.0.0", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-kep7JFn5Bil9/OUZUsL1bgoo0G9DmEf7stkBW67+NqP2FrzBt2TX8yz4V6oKLygzepGy90Ura2FtqXawYKXYIg=="], + "@storybook/csf-plugin/unplugin": ["unplugin@1.16.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],